hanalyze-0.2.0.0: demo/io/PreprocessDemo.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
-- | Hanalyze.DataIO.Preprocess の総合デモ。
--
-- - NA 文字列を含む CSV をロード (Hackage dataframe 経由)
-- - countMissing で欠損列を確認
-- - dropMissingRows / imputeMean / imputeMedian / imputeConstant の比較
-- - filterRowsByNumeric / mapNumeric / deriveNumeric の使用例
module Main where
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified DataFrame.Internal.DataFrame as DX
import qualified DataFrame.Operators as DX
import qualified DataFrame.Operations.Core as DX
import qualified DataFrame.Internal.DataFrame as DXD
import Hanalyze.DataIO.CSV (loadCSV)
import Hanalyze.DataIO.Preprocess
import System.IO (hPutStrLn, stderr)
import System.Exit (exitFailure)
import Text.Printf (printf)
testCSV :: String
testCSV = unlines
[ "group,age,income"
, "A,25,40000"
, "A,NA,42000"
, "B,32,"
, "B,28,55000"
, "C,,38000"
, "A,45,NA"
, "B,30,48000"
, "C,55,72000"
]
main :: IO ()
main = do
let path = "/tmp/preprocess_demo.csv"
writeFile path testCSV
result <- loadCSV path
case result of
Left err -> do
hPutStrLn stderr ("Parse error: " ++ err)
exitFailure
Right df -> runDemo df
runDemo :: DXD.DataFrame -> IO ()
runDemo df = do
putStrLn "=================================="
putStrLn " Hanalyze.DataIO.Preprocess Demo"
putStrLn "=================================="
putStrLn ""
let (nrows, _) = DX.dimensions df
printf "Loaded %d rows, columns: %s\n"
nrows (T.unpack (T.intercalate ", " (DX.columnNames df)))
putStrLn ""
putStrLn "--- countMissing ---"
mapM_ (\(c, m) ->
if m > 0 then printf " %s: %d missing\n" (T.unpack c) m
else printf " %s: complete\n" (T.unpack c))
(countMissing df)
putStrLn ""
putStrLn "--- dropMissingRows [\"age\", \"income\"] ---"
let df1 = dropMissingRows ["age", "income"] df
(nrows1, _) = DX.dimensions df1
printf " After: %d rows (was %d)\n" nrows1 nrows
putStrLn ""
putStrLn "--- parseNumericColumn ---"
case parseNumericColumn "age" df1 >>= parseNumericColumn "income" of
Nothing -> putStrLn " (already numeric or parse failed; OK if Hackage parsed it)"
Just df2 -> do
printf " Both age/income are now numeric\n"
showNumericStats df2 "age"
showNumericStats df2 "income"
putStrLn ""
putStrLn "--- imputeMean / imputeMedian on age ---"
case imputeMean "age" df of
Just df3 -> do
let (n3, _) = DX.dimensions df3
printf " imputeMean produces %d numeric rows\n" n3
showNumericStats df3 "age"
Nothing -> putStrLn " imputeMean failed"
case imputeMedian "income" df of
Just df4 -> do
let (n4, _) = DX.dimensions df4
printf " imputeMedian produces %d numeric rows\n" n4
showNumericStats df4 "income"
Nothing -> putStrLn " imputeMedian failed"
putStrLn ""
putStrLn "--- filterRowsByNumeric (age >= 30) ---"
let dfNum = case imputeMean "age" df >>= imputeMean "income" of
Just d -> d
Nothing -> df
dfFilt = filterRowsByNumeric "age" (>= 30) dfNum
(nNum, _) = DX.dimensions dfNum
(nFilt, _) = DX.dimensions dfFilt
printf " After: %d rows (was %d)\n" nFilt nNum
putStrLn ""
putStrLn "--- mapNumeric \"income\" (/1000) ---"
let dfMap = mapNumeric "income" (/ 1000) dfNum
showNumericStats dfMap "income"
putStrLn ""
putStrLn "--- deriveNumeric \"ratio\" = income / age ---"
let dfDeriv = deriveNumeric "ratio"
(\row -> case (Map.lookup "income" row, Map.lookup "age" row) of
(Just (VNum i), Just (VNum a)) | a > 0 -> i / a
_ -> 0)
dfNum
showNumericStats dfDeriv "ratio"
putStrLn ""
putStrLn "--- selectColumns [\"group\", \"age\"] ---"
let dfSel = selectColumns ["group", "age"] dfNum
printf " columns: %s\n" (T.unpack (T.intercalate ", " (DX.columnNames dfSel)))
putStrLn ""
putStrLn "Done."
showNumericStats :: DXD.DataFrame -> T.Text -> IO ()
showNumericStats df name =
case readNum name df of
Nothing -> printf " %s: not numeric\n" (T.unpack name)
Just xs -> do
let m = length xs
mean = sum xs / fromIntegral m
mn = minimum xs
mx = maximum xs
printf " %-10s n=%d min=%.2f max=%.2f mean=%.2f\n"
(T.unpack name) m mn mx mean
readNum :: T.Text -> DXD.DataFrame -> Maybe [Double]
readNum name df =
case DXD.getColumn name df of
Nothing -> Nothing
Just _ ->
case tryReadDouble name df of
Just xs -> Just xs
Nothing -> tryReadIntAsDouble name df
tryReadDouble :: T.Text -> DXD.DataFrame -> Maybe [Double]
tryReadDouble name df = either (const Nothing) Just $
fmap (map (id :: Double -> Double)) $
Right (DX.columnAsList (DX.col @Double name) df)
tryReadIntAsDouble :: T.Text -> DXD.DataFrame -> Maybe [Double]
tryReadIntAsDouble name df = either (const Nothing) Just $
fmap (map (fromIntegral :: Int -> Double)) $
Right (DX.columnAsList (DX.col @Int name) df)