使用 Deedle 的分类数据

Categorical data with Deedle

假设我有一个包含分类变量的 CSV 数据,例如

     Entry Color 
0 -> 1     Red   
1 -> 2     Blue  

我想将变量翻译成可区分的联合。我试过 row.GetAs<Color>,结果是 InvalidCastException。如果我使用 fromString/toString,我必须跟踪记录中哪个变量已经是 cast/read,哪个不是/从 csv 数据中读取。有更好的解决方案吗?


    #r "nuget: Deedle"
    
    open Deedle
    
    //
    module Util =
        open Microsoft.FSharp.Reflection
    
        let toString (x:'a) = 
            let (case, _ ) = FSharpValue.GetUnionFields(x, typeof<'a>)
            case.Name
    
        let fromString<'a> (s:string) =
            match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
            |[|case|] -> (FSharpValue.MakeUnion(case,[||]) :?> 'a)
            |_ -> failwith $"Unknown union case {s}"
    
    type Color =
        | Red
        | Blue
        | Green
        override this.ToString() =  Util.toString this
        static member fromString s = Util.fromString<Color> s
    
    
    let data = "Entry;Color\n1;Red\n2;Blue"
    
    //
    let bytes = System.Text.Encoding.UTF8.GetBytes data
    let stream =  new MemoryStream( bytes )
    
    let df:Frame<int,string> = Frame.ReadCsv(
        stream = stream,
        separators = ";",
        hasHeaders = true
    )
    
    df.Print()
    
    //let col = df |> Frame.mapRowValues (fun row -> row.GetAs<Color>"Color") 
    //Invalid cast from 'System.String' to 'FSI_...+Color'.
    
    let col' = df |> Frame.mapRowValues (fun row -> Color.fromString (row.GetAs<string> "Color"))
    //works 
    
    df.ReplaceColumn("Color", col')
    
    df.SaveCsv(__SOURCE_DIRECTORY__ + "/df.csv",includeRowKeys=false)
    
    let df' = Frame.ReadCsv(__SOURCE_DIRECTORY__ + "/df.csv", schema="int,Color")
    
    df |> Frame.mapRowValues (fun row -> row.GetAs<Color> "Color") 
    //works
    
    df' |> Frame.mapRowValues (fun row -> row.GetAs<Color> "Color") 
    //breaks

不幸的是,无法告诉 Deedle 在读取 CSV 数据时将特定列转换为可区分的联合。 (这不适用于具有带参数的 case 的联合,而且 Deedle 也不知道您的 F# 代码中定义了哪些类型。)

最好的方法是按照您当前正在做的事情 - 即,读取 CSV 文件,将分类值作为字符串,然后手动解析这些值并替换该列。我可能会通过获取指定的系列并使用 Series.mapValues 来转换数据(因为这比使用 Frame.mapRowValues 更直接):

let df = Frame.ReadCsv(stream = stream, separators = ";", hasHeaders = true)
let newCol = df.Columns.["Color"].As<string>() |> Series.mapValues Color.fromString
df.ReplaceColumn("Color", newCol)