使用 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)
假设我有一个包含分类变量的 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)