首页 > 解决方案 > F# 合并具有不同列的 CSV 文件

问题描述

我对 F# 还很陌生,但我对它很着迷,想将它应用到一些应用程序中。目前,我有多个 csv 文件,它们只是时间戳和一些传感器的值,时间戳是唯一的,但列值不同。例如我有两个 csv 文件

csv1:

timestamp, sensor1
time1, 1.0

csv2:

timestamp, sensor1, sensor2
time2, 2.0, 3.0

我想要的结果是

timestamp, sensor1, sensor2
time1, 1.0, 
time2, 2.0, 3.0

我想知道是否有任何简单的方法可以在 F# 中做到这一点。谢谢

更新 1:
我目前的解决方案涉及使用LumenWorks.Framework.IO.Csv ( https://www.nuget.org/packages/LumenWorksCsvReader ) 将 csv 解析为 Data.DataTable 和Deedle ( https://www.nuget. org/packages/Deedle ) 将 Data.DataTable 转换为 Frame 并使用 SaveCsv 方法保存到 csv 文件。

open System.IO
open System
open LumenWorks.Framework.IO.Csv
open Deedle

// get list of csv files
let filelist = expression_to_get_list_of_csv_file_path

// func to readCsv from path and return Data.DataTable
let funcReadCSVtoDataTable (path:string) = 
    use csv = new CachedCsvReader(new StreamReader(path), true)
    let tmpdata = new Data.DataTable()
    tmpdata.Load(csv)
    tmpdata        

// map list of file paths to get list of datatable
let allTables = List.map funcReadCSVtoDataTable filelist

// create allData table to iterate over the list
let allData = new Data.DataTable()
List.iter (fun (x:Data.DataTable) -> allData.Merge(x)) allTables

//convert datatable to Deedle Frame and save to csv file
let df = Frame.ReadReader (allData.CreateDataReader())   
df.SaveCsv("./final_csv.csv")

之所以使用LumenWorks.Framework.IO.Csv是因为我需要同时解析几千个文件,并且根据这篇文章(https://www.codeproject.com/Articles/11698/A-Portable -and-Efficient-Generic-Parser-for-Flat-F ) LumenWorks.Framework.IO.Csv是最快的。

更新 2:最终解决方案 感谢 Tomas 关于 RowsKey 地图解决方案(请参阅下面的评论),我重新扭曲了他的代码以用于文件列表的情况

// get list of csv files
let filelist = expression_to_get_list_of_csv_file_path

// function to merge two Frames
let domerge (df0:Frame<int,string>) (df1:Frame<int,string>) = 
    df1 
    |> Frame.mapRowKeys (fun k-> k+df0.Rows.KeyCount)
    |> Frame.merge df0

// read filelist to Frame list 
let dflist = filelist |> List.map (fun (x:string)-> Frame.ReadCsv x)

// using List.fold to "fold" through the list with dflist.[0] is the intial state
let dffinal = List.tail dflist |> List.fold domerge (List.head dflist)
dffinal.SaveCsv("./final_csv.csv")

现在代码看起来“功能正常”,但是,我收到 Frame.ReadCsv 的一个小警告,该方法不适用于 F#,但它仍然有效。

标签: csvf#

解决方案


如果您乐于使用外部库,那么您可以使用名为 Deedle 的数据框操作库非常轻松地做到这一点。Deedle 允许您从 CSV 文件中读取数据帧,当您合并数据帧时,它会确保为您对齐列键和行键:

open Deedle

let f1 = Frame.ReadCsv("c:/temp/f1.csv")
let f2 = Frame.ReadCsv("c:/temp/f2.csv")

let merged = 
  f2 
  |> Frame.mapRowKeys (fun k -> k + f1.Rows.KeyCount)
  |> Frame.merge f1

merged.SaveCsv("c:/temp/merged.csv")

我们必须在这里做的一件棘手的事情是使用mapRowKeys. 当您读取帧时,Deedle 会自动为您的数据生成有序行键,因此合并会失败,因为您有两行带有 key 0。该mapRowKeys函数允许我们转换键,使它们是唯一的并且可以合并帧。(保存 CSV 文件不会自动将行键写入输出,因此结果正是您想要的。)


推荐阅读