与 Deedle 交叉连接

Cross join with Deedle

我正在尝试通过分析我的电费来学习一些 F# 和 Deedle。

假设我有两个框架,其中一个包含我的用电量:

let consumptionsByYear = 
  [ (2019, "Total", 500); (2019, "Day", 200); (2019, "Night", 300);
    (2020, "Total", 600); (2020, "Day", 250); (2020, "Night", 350) ]
  |> Frame.ofValues
        Total Day Night 
2019 -> 500   200 300   
2020 -> 600   250 350   

另一个包含两个具有不同定价结构的计划(固定费用或费用根据一天中的时间而变化):

let prices = 
  [ ("Plan A", "Base fee", 50); ("Plan A", "Fixed price", 3); ("Plan A", "Day price", 0); ("Plan A", "Night price", 0);
    ("Plan B", "Base fee", 40); ("Plan B", "Fixed price", 0); ("Plan B", "Day price", 5); ("Plan B", "Night price", 2) ]
  |> Frame.ofValues
          Base fee Fixed price Day price Night price 
Plan A -> 50       3           0         0           
Plan B -> 40       0           5         2           

之前,我在 SQL 中使用交叉连接解决了这个问题,在 Excel 中使用嵌套连接解决了这个问题。为了复制这些,我找到了 Frame.mapRows,但是使用它构建预期的输出似乎非常乏味:

let costs = consumptionsByYear
            |> Frame.mapRows (fun _year cols ->
                ["Total price" => (prices?``Base fee``
                    + (prices?``Fixed price`` |> Series.mapValues ((*) (cols.GetAs<float>("Total"))))
                    + (prices?``Day price`` |> Series.mapValues ((*) (cols.GetAs<float>("Day"))))
                    + (prices?``Night price`` |> Series.mapValues ((*) (cols.GetAs<float>("Night"))))
                    )]
                |> Frame.ofColumns)
            |> Frame.unnest
               Total price 
2019 Plan A -> 1550        
     Plan B -> 1640        
2020 Plan A -> 1850        
     Plan B -> 1990        

是否有更好的方法或者更小的改进?

我不是Deedle专家,但我认为这基本上是:

  • 两个矩阵的点积:consumptionsByYear 和周期性 day/night 价格,
  • 随后添加不变的基本价格。

换句话说:

 consumptionsByYear      periodicPrices               basePrices
 -------------------     ------------------------     ---------------------------
|         Day Night |   |          Plan A Plan B |   |             Plan A Plan B |
| 2019 -> 200 300   | * | Day   -> 3      5      | + | Base fee -> 50     40     |
| 2020 -> 250 350   |   | Night -> 3      2      |    ---------------------------
 -------------------     ------------------------

考虑到这种方法,我会这样做:

open Deedle
open Deedle.Math

let consumptionsByYear = 
    [ (2019, "Day", 200); (2019, "Night", 300)
      (2020, "Day", 250); (2020, "Night", 350) ]
    |> Frame.ofValues

let basePrices =
    [ ("Plan A", "Base fee", 50)
      ("Plan B", "Base fee", 40) ]
    |> Frame.ofValues
    |> Frame.transpose

let periodicPrices =
    [ ("Plan A", "Day", 3); ("Plan A", "Night", 3)
      ("Plan B", "Day", 5); ("Plan B", "Night", 2) ]
    |> Frame.ofValues
    |> Frame.transpose

// repeat the base prices for each year
let basePricesExpanded =
    let row = basePrices.Rows.["Base fee"]
    consumptionsByYear
        |> Frame.mapRowValues (fun _ -> row)
        |> Frame.ofRows

let result =
    Matrix.dot(consumptionsByYear, periodicPrices) + basePricesExpanded
result.Print()

输出为:

        Plan A Plan B
2019 -> 1550   1640
2020 -> 1850   1990

为简单起见,我做了一些更改:

  • consumptionsByYear
    • 为了使矩阵兼容,我将年份从整数映射到字符串。
    • 我删除了 Total 列,因为它可以从其他两个导出。
  • prices
    • 我将其分成两个单独的框架:一个用于周期性价格,另一个用于基本价格,然后将它们转置以启用矩阵乘法。
    • 我将 Day price 更改为 Day 并将 Night price 更改为 Night 以使矩阵兼容。
    • 我删除了 Fixed price 列,因为它可以在 DayNight 列中表示。

更新:从 Deedle 2.4.2 开始,不再需要将年份映射到字符串。我已经相应地修改了我的解决方案。