2017-05-05 61 views
1

我期待以與pandas.DataFrame.Merge類似的方式基於每個幀中的特定列合併兩個Deedle(F#)幀。其中最好的例子是包含數據列的主幀和(城市,州)列以及包含以下列的信息框:(城市,州);土地增值稅;長。如果我想將緯度長列添加到我的主框架中,我會合並(城市,州)列上的兩個框架。與pandas.merge相當的指針

下面是一個例子:

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 

感謝您的幫助!

UPDATE:

交換Frame.indexRowsString到Frame.indexRows以應對在 'mergOnCol' 的類型不是字符串的情況。

擺脫infoFrame.Clone()的由Tomas

回答

0

方式的建議Deedle不(僅行/列鍵)幀的加盟黯然意味着它不會有一個很好的內置函數做通過非關鍵列連接幀。

據我所見,你的方法對我來說很好。你不需要infoFrame上的Clone(因爲你沒有改變幀),我認爲你可以用infoFrame.TryGetRow代替infoFrame.GetRow(然後你不需要事先得到密鑰),但除此之外,你的代碼看起來像精細!

我想出了一個替代和這樣做的有點短的方式,它看起來如下:

// 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) 

這是一個有點短,也許更不言自明,但我沒有做任何測試檢查哪一個更快。除非性能是主要問題,否則我認爲使用更易讀的版本是一個很好的默認選擇!

+0

感謝您的幫助。我在我的實際項目上運行了這個,並有一些評論。這工作除了我仍然需要添加一個「removeDuplicateRows」的情況下(堅持這個例子)的信息框架可能有兩次列出相同的「城市,國家」。此外,我無法讓我的一個infoFrames與這個函數合併,但它會與上面的一個合併。另外, –

+0

最後我跑了一些測試。在(行,列)中,主幀=(29635,7),第一信息幀=(2572,4),第二信息幀=(18601,2)。 5次合併所有三個平均5.97秒。用我的方法和27.05秒。與你的。儘管如此,感謝您對此進行調查並迅速回答! –

+0

不錯的工作,並感謝您運行測試:-)聽起來像你的方式是要走的路! –