Deedle Эквивалент pandas.merge
Я хочу объединить два кадра Deedle (F#) на основе определенного столбца в каждом кадре аналогично pandas.DataFrame.Merge. Прекрасным примером этого будет основной кадр, содержащий столбцы данных и (city, столбец) и информационный фрейм, содержащий следующие столбцы: (город, штат); лат; долго. Если я хочу добавить столбцы длиной в лат в свой основной кадр, я бы объединил два кадра в столбце (город, штат).
Вот пример:
let primaryFrame =
[(0, "Job Name", box "Job 1")
(0, "City, State", box "Reno, NV")
(1, "Job Name", box "Job 2")
(1, "City, State", box "Portland, OR")
(2, "Job Name", box "Job 3")
(2, "City, State", box "Portland, OR")
(3, "Job Name", box "Job 4")
(3, "City, State", box "Sacramento, CA")] |> Frame.ofValues
let infoFrame =
[(0, "City, State", box "Reno, NV")
(0, "Lat", box "Reno_NV_Lat")
(0, "Long", box "Reno_NV_Long")
(1, "City, State", box "Portland, OR")
(1, "Lat", box "Portland_OR_Lat")
(1, "Long", box "Portland_OR_Long")] |> Frame.ofValues
// see code for merge_on below.
let mergedFrame = primaryFrame
|> merge_On infoFrame "City, State" null
В результате чего 'mergedFrame' будет выглядеть так:
> mergedFrame.Format();;
val it : string =
" Job Name City, State Lat Long
0 -> Job 1 Reno, NV Reno_NV_Lat Reno_NV_Long
1 -> Job 2 Portland, OR Portland_OR_Lat Portland_OR_Long
2 -> Job 3 Portland, OR Portland_OR_Lat Portland_OR_Long
3 -> Job 4 Sacramento, CA <missing> <missing>
Я придумал способ сделать это (функция 'merge_on', использованная в примере выше), но, будучи инженером по продажам, который является новичком в F#, я думаю, что есть более идиоматический / эффективный способ сделать это. Ниже приведены мои функции для этого вместе с "removeDuplicateRows", который делает то, что вы ожидаете, и был необходим для функции "merge_on"; если вы хотите прокомментировать лучший способ сделать это, пожалуйста, сделайте.
let removeDuplicateRows column (frame : Frame<'a, 'b>) =
let nonDupKeys = frame.GroupRowsBy(column).RowKeys
|> Seq.distinctBy (fun (a, b) -> a)
|> Seq.map (fun (a, b) -> b)
frame.Rows.[nonDupKeys]
let merge_On (infoFrame : Frame<'c, 'b>) mergeOnCol missingReplacement
(primaryFrame : Frame<'a,'b>) =
let frame = primaryFrame.Clone()
let infoFrame = infoFrame
|> removeDuplicateRows mergeOnCol
|> Frame.indexRows mergeOnCol
let initialSeries = frame.GetColumn(mergeOnCol)
let infoFrameRows = infoFrame.RowKeys
for colKey in infoFrame.ColumnKeys do
let newSeries =
[for v in initialSeries.ValuesAll do
if Seq.contains v infoFrameRows then
let key = infoFrame.GetRow(v)
yield key.[colKey]
else
yield box missingReplacement ]
frame.AddColumn(colKey, newSeries)
frame
Спасибо за вашу помощь!
ОБНОВИТЬ:
Переключен Frame.indexRowsString на Frame.indexRows для обработки случаев, когда типы в 'mergOnCol' не являются строками.
Избавился от infoFrame.Clone() в соответствии с предложением Томаса
1 ответ
То, как Deedle выполняет объединение фреймов (только в ключах строк / столбцов), к сожалению, означает, что у него нет хорошей встроенной функции для объединения фреймов по неключевому столбцу.
Насколько я понимаю, ваш подход выглядит очень хорошо для меня. Вы не должны Clone
на infoFrame
(потому что вы не мутируете кадр) и я думаю, что вы можете заменить infoFrame.GetRow
с infoFrame.TryGetRow
(и тогда вам не нужно будет получать ключи заранее), но кроме этого, ваш код выглядит хорошо!
Я придумал альтернативный и немного более короткий способ сделать это, который выглядит следующим образом:
// Index the info frame by city/state, so that we can do lookup
let infoByCity = infoFrame |> Frame.indexRowsString "City, State"
// Create a new frame with the same row indices as 'primaryFrame'
// containing the additional information from infoFrame.
let infoMatched =
primaryFrame.Rows
|> Series.map (fun k row ->
// For every row, we get the "City, State" value of the row and then
// find the corresponding row with additional information in infoFrame. Using
// 'ValueOrDefault' will automatically give missing when the key does not exist
infoByCity.Rows.TryGet(row.GetAs<string>("City, State")).ValueOrDefault)
// Now turn the series of rows into a frame
|> Frame.ofRows
// Now we have two frames with matching keys, so we can join!
primaryFrame.Join(infoMatched)
Это немного короче и может быть более понятным, но я не сделал никаких тестов, чтобы проверить, что быстрее. Если производительность не является основной проблемой, я думаю, что использование более читаемой версии является хорошим выбором по умолчанию!