F # объединить файлы CSV с разными столбцами - PullRequest
0 голосов
/ 29 августа 2018

Я довольно новичок в 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: ЗАКЛЮЧИТЕЛЬНОЕ РЕШЕНИЕ Благодаря Томасу о решении карты 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 #, но все равно работает.

Ответы [ 2 ]

0 голосов
/ 29 августа 2018

Если вы счастливы использовать внешнюю библиотеку, то вы можете сделать это очень легко, используя библиотеку манипуляции с фреймом данных под названием 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 автоматически не записываются ключи строк в вывод, поэтому результат этого - именно то, что вы хотели.)

0 голосов
/ 29 августа 2018

Если вы выполняете много подобной обработки, вам следует обратиться к 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...