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)

Это немного короче и может быть более понятным, но я не сделал никаких тестов, чтобы проверить, что быстрее. Если производительность не является основной проблемой, я думаю, что использование более читаемой версии является хорошим выбором по умолчанию!

Другие вопросы по тегам