我对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)< em> 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#,但仍然可以使用。
答案 0 :(得分:2)
如果您愿意使用外部库,则可以使用data frame manipulation library called 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会自动为您的数据生成序数行键,因此合并将失败,因为您有两行带有键0
。通过mapRowKeys
函数,我们可以变换键,以便它们是唯一的,并且可以合并帧。 (保存CSV文件不会自动将行键写入输出,因此其结果正是您想要的。)
答案 1 :(得分:1)
如果您要进行大量此类处理,则应查看CSV TypeProvider和Parser或我最喜欢的FileHelpers。
如果您不想使用任何第三方库,以下是一个快速的分步过程,以读取,重新组装和写出文件:
open System.IO
open System
let csv1path = @"E:\tmp\csv1.csv"
let csv2path = @"E:\tmp\csv2.csv"
/// Read the file, split it up, and remove the header from the first csv file
let csv1 =
File.ReadAllLines(csv1path)
|> Array.map (fun x -> x.Split(','))
|> Array.tail
let csv2 =
File.ReadAllLines(csv2path)
|> Array.map (fun x -> x.Split(','))
///Split the header and data in the second csv file
let header', data = (csv2.[0], Array.tail csv2)
let header = String.Join(",", header')
///put back the data together, this is an array of arrays
let csv3 =
Array.append(csv1) data
///Sort the combined file, put it back together as a csv and add back the header
let csv4 =
csv3
|> Array.sort
|> Array.map (fun x -> String.Join(",", x))
|> Array.append [|header|]
///Write it out
File.WriteAllLines(@"E:\tmp\combined.csv",csv4)