使用静态类型语言处理异构数据(F#)

时间:2009-11-25 18:15:36

标签: c# .net f#

F#的一个主张是它允许交互式脚本和数据操作/探索。我一直在玩F#试图了解它与Matlab和R在数据分析工作中的比较。显然,F#没有这些生态系统的所有实用功能,但我对底层语言的一般优点/缺点更感兴趣。

对我来说,即使在功能风格方面,最大的变化是F#是静态类型的。这有一些吸引力,但也经常感觉像一件紧身衣。例如,我还没有找到一种处理异构矩形数据的便捷方法 - 想想R中的数据帧。假设我正在读取一个带有名称(字符串)和权重(浮点数)的CSV文件。通常我会加载数据,执行一些转换,添加变量等,然后运行分析。在R中,第一部分可能看起来像:

df <- read.csv('weights.csv')
df$logweight <- log(df$weight)

在F#中,我不清楚应该用什么结构来做这件事。据我所知,我有两个选择:1)我可以首先定义一个强类型的类(专家F#9.10)或2)我可以使用异构容器,如ArrayList。静态类型的类似乎不可行,因为我需要在加载数据后以ad-hoc方式(logweight)添加变量。异构容器也不方便,因为每次访问变量时我都需要将其解包。在F#中:

let df = readCsv("weights.csv")
df.["logweight"] = log(double df.["weight"])

如果这是一次或两次,它可能没问题,但是我使用变量指定类型每个时间似乎并不合理。我经常处理调查,其中包含100个添加/删除的变量,分成新的子集并与其他数据帧合并。

我错过了一些明显的第三选择吗?是否有一些有趣和轻松的方式来交互和操纵异构数据?如果我需要在.Net上进行数据分析,我现在的意思是我应该使用IronPython进行所有数据探索/转换/交互工作,并且只对数字密集型部分使用F#/ C#。 F#本质上是快速和脏的异构数据工作的错误工具吗?

3 个答案:

答案 0 :(得分:8)

  

F#本身就是错误的工具   快速而肮脏的异构数据   工作?

对于完全临时的,探索性的数据挖掘,我不建议使用F#,因为类型会妨碍你。

但是,如果你的数据定义得很好,那么你可以通过将所有类型映射到一个普通的F#union来在同一个容器中保存不同的数据类型:

> #r "FSharp.PowerPack";;

--> Referenced 'C:\Program Files\FSharp-1.9.6.16\bin\FSharp.PowerPack.dll'

> let rawData =
    "Name: Juliet
     Age: 23
     Sex: F
     Awesome: True"

type csv =
    | Name of string
    | Age of int
    | Sex of char
    | Awesome of bool

let parseData data =
    String.split ['\n'] data
    |> Seq.map (fun s ->
        let parts = String.split [':'] s
        match parts.[0].Trim(), parts.[1].Trim() with
        | "Name", x -> Name(x)
        | "Age", x -> Age(int x)
        | "Sex", x -> Sex(x.[0])
        | "Awesome", x -> Awesome(System.Convert.ToBoolean(x))
        | data, _ -> failwithf "Unknown %s" data)
    |> Seq.to_list;;

val rawData : string =
  "Name: Juliet
     Age: 23
     Sex: F
     Awesome: True"
type csv =
  | Name of string
  | Age of int
  | Sex of char
  | Awesome of bool
val parseData : string -> csv list

> parseData rawData;;
val it : csv list = [Name "Juliet"; Age 23; Sex 'F'; Awesome true]

csv list是强类型的,您可以对其进行模式匹配,但您必须预先定义所有的联合构造函数。

我个人更喜欢这种方法,因为比使用无类型的ArrayList要好几个数量级。但是,我不确定你的要求是什么,我不知道一个很好的方法来表示特殊变量(除了可能是Map{string, obj})所以YMMV。

答案 1 :(得分:6)

我认为还有其他一些选择。

(?)运营商

正如Brian所说,您可以使用(?)运算符:

type dict<'a,'b> = System.Collections.Generic.Dictionary<'a,'b>

let (?) (d:dict<_,_>) key = unbox d.[key]
let (?<-) (d:dict<_,_>) key value = d.[key] <- box value

let df = new dict<string,obj>()
df?weight <- 50.
df?logWeight <- log(df?weight)

这会在每次访问时使用装箱/取消装箱,有时您可能需要添加类型注释:

(* need annotation here, since we could try to unbox at any type *)
let fltVal = (df?logWeight : float)

顶级标识符

另一种可能性是,不是动态定义现有对象的属性(F#不支持特别好),您可以使用顶级标识符。

let dfLogWeight = log(dfWeight)

这样做的好处是,您几乎不需要指定类型,但它可能会混乱您的顶级命名空间。

属性对象

需要更多输入和更粗略语法的最终选项是创建强类型“属性对象”:

type 'a property = System.Collections.Generic.Dictionary<obj,'a>

let createProp() : property<'a> = new property<'a>()
let getProp o (prop:property<'a>) : 'a = prop.[o]
let setProp o (prop:property<'a>) (value:'a) = prop.[o] <- value

let df = new obj()
let (weight : property<double>) = createProp()
let (logWeight : property<double>) = createProp()

setProp df weight 50.
setProp df logWeight (getProp df weight)
let fltVal = getProp df logWeight

这要求显式创建每个属性(并且在该点需要类型注释),但之后不需要类型注释。我发现这比其他选项的可读性低得多,尽管定义一个替换getProp的运算符可能会有所缓解。

答案 2 :(得分:1)

我不确定F#在这里是否是一个很棒的工具。但还有第三种选择 - 问号运算符。我一直想博客谈论这个问题一段时间了; Luca's recent PDC talk演示了一个带有C#'dynamic'的CSV阅读器,我想使用(?)运算符用F#编写类似的东西。参见

F# operator "?"

简短说明。你可以尝试自己开始并自己玩这个,或者等我发表关于它的博客。我没有认真地尝试过,所以我不确定它会有多好用。

修改

我应该补充一点,Luca的演讲显示C#中的“动态”如何解决该语言的至少一部分问题。

修改

另见

http://cs.hubfs.net/forums/thread/12622.aspx

我发布了一些基本的入门CSV代码。