Skip to content

Commit

Permalink
Issue #17 (#41)
Browse files Browse the repository at this point in the history
* add example

* add fixtures, handle read errors

* get rid of errors using hlint
  • Loading branch information
UnkDevE authored and ocramz committed Mar 2, 2019
1 parent 5594bd2 commit b681743
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 0 deletions.
118 changes: 118 additions & 0 deletions analyze/examples/MoneySpent.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
module MoneySpent
(
main,
Prices (..),
Items (..)
)
where


import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Data.Text.Read as TR
import qualified Data.Text.Show as TS
import qualified Data.Vector as V

import Control.Monad.Catch (catch)
import Data.Maybe (fromJust, isJust, maybe)

import qualified Anaylze.RFrame as F
import qualified Anaylze.Common as C
import qualified Anaylze.CSV as CSV

lookupFilter :: (Data k) =>
(k -> Bool) -> Vector k -> HashMap k Int -> [Int]
lookupFilter pred keys lkup = map ((flip HM.lookup) lkup)
$ V.filter pred keys

removeLegalFees :: (Data v, MonadThrow m) =>
RFrame T.Text v -> RFrame T.Text v -> RFrame T.Text v
removeLegalFees =
F.filter (\keys lkup i values -> not $
(T.isInfixOf (T.pack "legal fees") $ values !!
lookupFilter (T.isInfixOf (T.pack "item")) keys lkup))

groupBy :: (Data v, MonadThrow m) =>
RFrame T.Text v -> T.Text -> m [RFrame T.Text v]
groupBy items tCol = V.mapM (\item -> F.filter (\keys lkup i values ->
item == values !! lookupFilter tCol keys lkup) items)
$ col tCol items

addPriceCol :: (Data v, MonadThrow m) =>
[RFrame T.Text v] -> RFrame T.Text v -> m [RFrame T.Text v]
addPriceCol splitItems prices = do
priceTag <- col (T.pack "price") prices

return $ V.map (\(s,p) ->
addColumn s (T.pack "price") $ V.take
(length $ rframeData s / length $ rframeKeys s)
$ V.fromList $ repeat p)
$ V.zip splitItems priceTag

merge :: (Data v, MonadThrow m) =>
m [R.Frame T.Text v] -> m (R.Frame T.Text v)
merge = foldM appendRows F.empty

filterDates :: (Data v) =>
R.Frame T.Text v -> R.Frame T.Text v
filterDates frame =
F.filter (\keys lkup i values ->
let date = (TR.readMaybe $ values !!
lookupFilter (T.pack "date") keys lkup) :: Maybe Int
in maybe False ((>) 6) date)

totalPrice ::
V.Vector T.Text -> V.Vector (V.Vector T.Text) -> Maybe Double
totalPrice keys values = (*) <$> price <*> units
where
lookupRead str = (TR.reads $ values !!
lookupFilter (T.pack str) keys (C.makeLookup keys)) :: Double
price = lookupRead "price"
units = lookupRead "units"

accumSumCol :: (Data v) =>
RFrame T.Text v -> V.Vector T.Text
accumSumCol items =
V.map TS.show $ V.foldl (\acc values ->
let price = totalPrice keys values
in if isJust price then
snoc acc $ (last acc) +
(fromJust price)
else acc
) V.empty (rframeData items)
where keys = rframeKeys items

mean :: RFrame T.Text T.Text -> Double
mean items =
V.foldl (\acc values ->
let price = totalPrice keys values
in if isJust price then
acc + price
else
acc
)
(0::Double) (rframeData items) / (V.length $ rframeData items)
where keys = rframeKeys items


main = do
-- Load Csv files
prices <- CSV.loadCsvFileWithHeader "./data/prices.csv"
items <- CSV.loadCsvFileWithHeader "./data/items.csv"

-- remove Legal fees
let pricesWithoutL = removeLegalFees prices
let itemsWithoutL = removeLegalFees items

-- merge price and purchase data
splitItems <- groupBy itemsWithoutL (T.pack "item-bought")
priceItems <- merge $ addPriceCol splitItems pricesWithoutL

-- filter dates, group by people
-- get accumlative sum and append it to the groups, then merge them
accsum <- mapM (\person -> addColumn person (T.pack "accsum") accumSumCol)
$ groupBy (filterDates priceItems) (T.pack "person")

let finalItems = merge accsum
--show mean
print $ mean finalItems
6 changes: 6 additions & 0 deletions analyze/examples/data/items.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
date , person , item-bought , units-bought
7 , bob , car , 1
5 , alice , car , 1
4 , bob , legal fees (1 hour) , 20
3 , alice , computer , 2
1 , bob , computer , 1
4 changes: 4 additions & 0 deletions analyze/examples/data/prices.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
item , price
computer , 1000
car , 5000
legal fees (1 hour) , 400
68 changes: 68 additions & 0 deletions analyze/test/Money.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module Main where

import qualified Analyze as A
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.Quickcheck
import Data.Text.Encoding (decodeUtf8, encodeUtf8)

import MoneyFixtures
import MoneySpent

testRemoveLegalFees :: TestTree
testRemoveLegalFees = testCase "removeLegalFees" $ do
table <- decodeWithHeader $ encodeUtf8 exampleUnitsCsv
noFees <- decodeWithHeader noLegalFees
removeLegalFees table ?@= noFees

testGroupBy :: TestTree
testGroupBy = testCase "groupBy (person)" $ do
grouped <- mapM decodeWithHeader personsGrouped
table <- decodeWithHeader $ encodeUtf8 exampleUnitsCsv
people <- groupBy table (T.pack "person")
grouped ?@= people

testWithPriceCol :: TestTree
testWithPriceCol = testCase "addPriceCol" $ do
table <- decodeWithHeader $ encodeUtf8 exampleUnitsCsv
prices <- decodeWithHeader $ encodeUtf8 examplePriceCsv
examplePC <- mapM decodeWithHeader priceCol
splitItems <- groupBy table (T.pack "item-bought")
priceCol <- addPriceCol splitItems prices
priceCol ?@= examplePC

testDatesFiltered :: TestTree
testDatesFiltered = testCase "datesFiltered" $ do
table <- decodeWithHeader $ encodeUtf8 exampleUnitsCsv
filtered <- decodeWithHeader datesFiltered
filterDates table ?@= filtered

testAccSum :: TestTree
testAccSum = testCase "accumSumCol" $ do
table <- decodeWithHeader $ head personsGrouped
accumSumCol table ?@= accumSum

testMerge :: TestTree
testMerge = testCase "merge" $ do
tables <- mapM decodeWithHeader personsGrouped
full <- encodeWithHeader $ encodeUtf8 exampleUnitsCsv
merge tables ?@= full

testMean :: TestTree
testMean = testCase "mean" $ do
pc <- mapM decodeWithHeader withPriceCol
table <- merge pc
mean table ?@= totalMean

tests :: TestTree
tests = [
testGroupBy,
testWithPriceCol,
testDatesFiltered,
testAccSum,
testMerge,
testMean
]

main :: IO ()
main = defaultMain tests
48 changes: 48 additions & 0 deletions analyze/test/MoneyFixtures.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module MoneyFixtures where

import qualified Data.Text as T
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Vector as V

import Data.Text (Text)
import Data.Vector (Vector)
import Data.Text.Encoding (decodeUtf8, encodeUtf8)

noLegalFees :: LBS.ByteString
noLegalFees = encodeUtf8 (T.pack ("date,item-bought,person,units\n" `mappend`
"6,motorbike,bob,2\n" `mappend` "6,computer,alice,2"))

datesFiltered :: LBS.ByteString
datesFiltered = encodeUtf8
(T.pack $ "date,item-bought,person,units\n" `mappend`
"5,legal fees,bob,3\n")

personsGrouped :: [LBS.ByteString]
personsGrouped = map encodeUtf8 [bob, alice]
where bob = T.pack "date,item-bought,person,units\n" `mappend`
"Legal fees,bob,3\n" `mappend` "5,motorbike,bob,2\n"
alice = T.pack "date,item-bought,person,units\n" `mappend`
"6,computer,alice,2"

withpriceCol :: [LBS.ByteString]
withpriceCol = map encodeUtf8 [bob, alice]
where bob = T.pack "date,item-bought,person,units,price\n" `mappend`
"legal fees,bob,3,300\n" `mappend` "5,motorbike,bob,2,200\n"
alice = T.pack "date,item-bought,person,units\n" `mappend`
"6,computer,alice,2,100"

accumSum :: Vector Text
accumSum = V.fromList $ ["900", "1300"]

totalMean :: Double
totalMean = 500

examplePriceCsv :: Text
examplePriceCsv = "item,price\n" `mappend` "legal fees,300" `mappend`
"motorbike,200" `mappend` "computer,100"

exampleUnitsCsv :: Text
exampleUnitsCsv = "date,item-bought,person,units\n" `mappend`
"5,legal fees,bob,3\n" `mappend` "6,computer,alice,2" `mappend`
"6,motorbike,bob,2"

0 comments on commit b681743

Please sign in to comment.