I have been struggling with F# and especially working with Deedle.Frame and Deedle.Series.
- I need to create two new columns with values that depend on the other columns.
- For some rows, I need to insert a new row.
My current solution is very ugly, basically filter the Frame according to each if-statement and return a new Frame with the new columns only. Then I merge this into the original Frame. This works since the cases or mutually exclusive. The case when I need to insert a new row is made such that I will reindex the new frame so that the during a merge it simply be appended at the bottom (after last row) of the original Frame.
However, since the different if-cases/patterns are many, I have to work with a lot of merging of frames and make sure that the complement of the IF-statements are handled (otherwise there will be missing values).
// ****** PSEUDO CODE ******
// Create new columns as copies.
Add column ["NEWTYPE"] = ["TYPE"]
Add column ["NEWVALUE"] = ["VALUE"]
// Iterate over rows.
foreach row in table
IF row["GROUP"] == A
typeParts = row["TYPE"].Split('/')
// Swap order X/Y -> Y/X.
IF typeParts[0] == "X" && typeParts[1] != "X"
row["NEWTYPE"] = typeParts[1] + "/" + "X"
row["NEWVALUE"] = row["VALUE"] / 10.0
// Split row into two rows Z/Y -> {Z/X, Y/X}.
ElseIF typeParts[0] != "X" && typeParts[1] != "X"
Insert extraRow = row
extraRow["NEWTYPE"] = typeParts[0] + "/" + "X"
extraRow.["NEWVALUE"] = row["VALUE"] * 2.0
row["NEWTYPE"] = typeParts[1] + "/" + "X"
row["NEWVALUE"] = row["VALUE"] * 3.0
ELSE
// Do nothing, new columns already copied.
ELSE
// Do nothing, new columns already copied.
A Gold star for anyone able to come up with a good solution. I guess it could be solved by returning a List of Frames (since a new row might be created) and then flatten and mergeAll?
***** Here is my current ugly F# code: *****
let subA =
inputFrame
|> Frame.filterRowValues(fun row -> row.GetAs<string>("GROUP") = "A")
let grpSwap =
subA
|> Frame.filterRowValues(fun row ->
let typeParts = row.GetAs<string>("TYPE").Split('/')
typeParts.[0] = "X" && typeParts.[1] <> "X")
|> Frame.mapRowValues(fun r ->
let typeParts = r.GetAs<string>("TYPE").Split('/')
series ["NEWTYPE" => box (typeParts.[1] + "/" + typeParts.[0]); "NEWVALUE" => box (r.GetAs<float>("VALUE") / 10.0)])
|> Frame.ofRows
let grpCopy =
subA
|> Frame.filterRowValues(fun row ->
let typeParts = row.GetAs<string>("TYPE").Split('/')
typeParts.[0] <> "X" && typeParts.[1] = "X")
|> Frame.mapRowValues(fun r ->
series ["NEWTYPE" => box (r.GetAs<string>("TYPE")); "NEWVALUE" => box (r.GetAs<float>("VALUE"))])
|> Frame.ofRows
let rowsToSplit =
subA
|> Frame.filterRowValues(fun row ->
let typeParts = row.GetAs<string>("TYPE").Split('/')
typeParts.[0] <> "X" && typeParts.[1] <> "X")
let grpSplit1 =
rowsToSplit
|> Frame.mapRowValues(fun r ->
let typeParts = r.GetAs<string>("TYPE").Split('/')
series ["NEWTYPE" => box (typeParts.[0] + "/" + "X"); "NEWVALUE" => box (r.GetAs<float>("VALUE") * 2.0)])
|> Frame.ofRows
let grpSplit2 =
rowsToSplit
|> Frame.mapRowValues(fun r ->
let typeParts = r.GetAs<string>("TYPE").Split('/')
series ["NEWTYPE" => box (typeParts.[1] + "/" + "X"); "NEWVALUE" => box (r.GetAs<float>("VALUE") * 3.0)])
|> Frame.ofRows
let grpAComplement =
inputFrame
|> Frame.filterRowValues(fun row ->
row.GetAs<string>("GROUP") <> "A")
|> Frame.mapRowValues(fun r ->
series ["NEWTYPE" => box (r.GetAs<string>("TYPE")); "NEWVALUE" => box (r.GetAs<float>("VALUE"))])
|> Frame.ofRows
let outputFrame =
let final0 = Frame.mergeAll([inputFrame; grpSwap; grpCopy; grpSplit1; grpAComplement])
let appendFromIndex = (final0.RowCount)
let appendToIndex = appendFromIndex + (grpSplit2.RowCount-1)
let newRow = grpSplit2 |> Frame.merge rowsToSplit
newRow |> Frame.indexRowsWith [appendFromIndex..appendToIndex] |> Frame.merge final0