I've been trying to move the legend in a plotly plot to the top or the bottom of the plot to maintain uniform sizes of plots with different legnds but I have been unable to do so. Even the plotly documentation has not been very helpful. Does anyone know how to do this?
I tried moving the legend for this example code but I have not been successfull.
let chart = Chart.Scatter(xs,
[10.; 20.; nan; 15.; 10.; 5.; 15.;nan; 20.; 10.; 10.; 15.; 25.; 20.; 10.],
StyleParam.Mode.Lines_Markers, Name="<b>No</b> Gaps")
|> GenericChart.mapTrace (Trace2DStyle.Scatter(ConnectGaps = true))
[
chart;
Chart.Scatter(xs,
[5.; 15.; nan; 10.; 5.; 0.; 10.; nan; 15.; 5.; 5.; 10.; 20.; 15.; 5.],
StyleParam.Mode.Lines_Markers, Name="Gaps")
] |> Chart.combine
Thanks!
This GitHub comment gives some relevant info for Plotly.NET 2.0. In short, you can move the legend to the top of the plot like this:
open Plotly.NET.LayoutObjects
let myLegend =
Legend.init(
Orientation = StyleParam.Orientation.Horizontal,
Y = 1.1
)
...
|> Chart.combine
|> Chart.withLegend(myLegend)
Setting Y to 0.0 instead will put the legend below the chart.
More details on Legend.init can be found here.
There are not examples for all the options, but if you use the search bar on the plotly.net website you can search for the relevant functions that show various options.
You probably want the Chart.withLegendStyle function. You can use the X and Y parameters to move the legend. The function documentation in Plotly.NET's API documentation describes the parameters and options.
Some sample data:
#r "nuget: Plotly.NET"
open Plotly.NET
let xs = [1.0 .. 4.0]
let y1 = xs |> List.map (fun x ->
-4.0 + if x = 3.0 then nan else x*1.0)
let y2 = xs |> List.map (fun x ->
3.0 + if x = 2.0 then nan else -x*2.0)
To place the legend below you only have to change the legend orientation, because horizontal legends default to being placed below:
[
Chart.Scatter(xs,y1,
StyleParam.Mode.Lines_Markers,
Name="<b>No</b> Gaps")
|> GenericChart.mapTrace (Trace2DStyle.Scatter(ConnectGaps = true))
Chart.Scatter(xs,y2,
StyleParam.Mode.Lines_Markers,
Name="Gaps")
]
|> Chart.combine
|> Chart.withLegendStyle(Orientation=StyleParam.Orientation.Horizontal)
|> Chart.show
To place the legend above, you must also change the Y parameter:
[
Chart.Scatter(xs,y1,
StyleParam.Mode.Lines_Markers,
Name="<b>No</b> Gaps")
|> GenericChart.mapTrace (Trace2DStyle.Scatter(ConnectGaps = true))
Chart.Scatter(xs,y2,
StyleParam.Mode.Lines_Markers,
Name="Gaps")
]
|> Chart.combine
|> Chart.withLegendStyle(Orientation=StyleParam.Orientation.Horizontal,
Y=1.1)
Related
I am trying to display a scatter chart for two columns in a Deedly data frame, ideally grouped by a third column.
And I would like to show a linear regression line on the same chart.
In Python this can be done with seaborn.lmplot https://seaborn.pydata.org/generated/seaborn.lmplot.html
sns.lmplot(data=penguins, x="bill_length_mm", y="bill_depth_mm", hue="species")
I was hoping to do something like that with Plotly.Net, but so far I only got a simple scatterplot:
(
df.["rating"].Values,
df.["calories"].Values
)
||> Seq.zip
|> Chart.Point
How do I add a linear regression line similar to seaborn? Do I need to do it manually somehow?
How do I group the points by a third column? This one I may be able to figure out myself, but I wonder if there is a more elegant solution.
Thanks to Brian Berns' comment, pointing me to this example, I was able to create a helper function that works similar to Python's seaborn.lmplot function.
Here is the code if anyone wants to use it:
// helpers
let getColVector col (df: Frame<'a, 'b>) =
vector <| df.[col].Values
let filterByKey fn (df: Frame<'a, 'b>) =
df.Where(fun (KeyValue(k, _)) -> fn k)
let singleGroupLmplot xCol yCol valuesName df =
let y = df |> getColVector yCol
let x = df |> getColVector xCol
let coefs = OrdinaryLeastSquares.Linear.Univariable.coefficient x y
let fittinFunc x = OrdinaryLeastSquares.Linear.Univariable.fit coefs x
let xRange = [for i in Seq.min(x)..Seq.max(x) -> i]
let yPredicted = [for x in xRange -> fittinFunc x]
let xy = Seq.zip xRange yPredicted
[
Chart.Point(x, y, ShowLegend=true, Name=valuesName)
|> Chart.withXAxisStyle(TitleText=xCol)
|> Chart.withYAxisStyle(TitleText=yCol)
Chart.Line(xy, ShowLegend=true, Name=($"Reg. {valuesName}"))
]
|> Chart.combine
let lmplot xCol yCol hue df =
match hue with
| None ->
[ singleGroupLmplot xCol yCol ($"{xCol} vs {yCol}") df ]
| Some h ->
let groupedDf = df |> Frame.groupRowsByString h
groupedDf.RowKeys
|> Seq.map (fun (g, _) -> g)
|> Seq.distinct
|> List.ofSeq
|> List.map (fun k ->
groupedDf
|> filterByKey (fun (g, _) -> g = k)
|> singleGroupLmplot xCol yCol k
)
|> Chart.combine
|> Chart.withLegendStyle(Orientation=StyleParam.Orientation.Horizontal)
Example of using it to render a scatter plot with regression line for a dataframe:
df |> lmplot "rating" "calories" None
Example of using it to render a scatter plots with regression lines for a dataframe grouped by a row value:
df |> lmplot "rating" "calories" (Some "healthiness")
I have seq<Nullable<int>> and need to create nice plot, but without null values.
Here is my code:
open System
#r """..\packages\FSharp.Charting.0.90.14\lib\net40\FSharp.Charting.dll"""
#load """..\packages\FSharp.Charting.0.90.14\FSharp.Charting.fsx"""
open FSharp.Charting
//in a real world replaced by .csv with empty values
let seqWithNullInt = seq[Nullable 10 ; Nullable 20 ; Nullable (); Nullable 40; Nullable 50]
//let seqWithNullInt = seq[ 10 ; 20 ; 30; 40; 50] //works fine
let bothSeq = seqWithNullInt |> Seq.zip {1..5}
Chart.Line bothSeq // Error because of nullable int
And here is my vision:
How to skip null values? I don't want to replace them by nearest or something, I need to skip them from chart.. Is there any solution?
Something like this might work (note that I've used Option values instead of nullables, since that's more idiomatic in F#):
let neitherPairHasNoneInValue (pair1, pair2) =
pair1 |> snd |> Option.isSome && pair2 |> snd |> Option.isSome
let seqWithNone = Seq.ofList [Some 10; Some 20; None; Some 40; Some 50]
let pairsWithoutNone = seqWithNone
|> Seq.zip {1..5}
|> Seq.pairwise
|> Seq.filter neitherPairHasNoneInValue
printfn "%A" pairsWithoutNone
This will output [(1,10),(2,20) ; (4,40),(5,50)]. I don't know the FSharp.Charting API offhand so I can't tell you which function will take that list of X,Y pairs and draw the graph you want, but it should be relatively straightforward to get from there to your graph.
So I have verified that the starting version of what I'm trying to do works, but for some reason when putting it into the Matrix.map high order function it breaks down.
Here is the failing function:
let SumSquares (theta:Vector<float>) (y:Vector<float>) (trainingData:Matrix<float>) =
let m = trainingData.RowCount
let theta' = theta.ToRowMatrix()
trainingData
|> Matrix.mapRows(fun a r -> (theta' * r) - y.[a] )
Here are some sample tests
Set up:
let tData = matrix [[1.0; 2.0]
[1.0; 3.0]
[1.0; 3.0]
[1.0; 4.0]]
let yVals = vector [5.0; 6.0; 7.0; 11.0]
let theta = vector [1.0; 0.2]
Test raw functionality of basic operation (theta transpose * vector - actual)
let theta' = theta.ToRowMatrix()
(theta.ToRowMatrix() * tData.[0, 0 .. 1]) - yVals.[0]
Testing in actual function:
tData |> SumSquares theta yVals
Here is a copy/paste of actual error. It reads as though its having issues of me mapping a larger vector to a smaller vector.
Parameter name: target
at MathNet.Numerics.LinearAlgebra.Storage.VectorStorage1.CopyToRow(MatrixStorage1 target, Int32 rowIndex, ExistingData existingData)
at FSI_0061.SumSquares(Vector1 theta, Vector1 y, Matrix`1 trainingData) in C:\projects\deleteme\ASPNet5Test\ConsoleApplication1\ConsoleApplication1\MachineLearning.fsx:line 23
at .$FSI_0084.main#() in C:\projects\deleteme\ASPNet5Test\ConsoleApplication1\ConsoleApplication1\MachineLearning.fsx:line 39
Stopped due to error
I found an even better easier way to do this. I have to credit s952163 for starting me down a good path, but this approach is even more optimized:
let square (x:Vector<float>) = x * x
let subtract (x:Vector<float>) (y:Vector<float>) = y - x
let divideBy (x:float) (y:float) = y / x
let SumSquares (theta:Vector<float>) (y:Vector<float>) (trainingData:Matrix<float>) =
let m = trainingData.RowCount |> float
(trainingData * theta)
|> subtract y
|> square
|> divideBy m
Since you know the number of rows you can just map to that. Arguably this is not pretty:
let SumSquares (theta:Vector<float>) (y:Vector<float>) (trainingData:Matrix<float>) =
let m = trainingData.RowCount
let theta' = theta.ToRowMatrix()
[0..m-1] |> List.map (fun i -> (((theta' * trainingData.[i,0..1]) |> Seq.exactlyOne) - yVals.[i] ))
Edit:
My guess is that mapRows wants everything to be in the same shape, and your output vector is different. So if you want to stick to the Vector type, this will just enumerate the indexed rows:
tData.EnumerateRowsIndexed() |> Seq.map (fun (i,r) -> (theta' * r) - yVals.[i])
and you can also use Matrix.toRowSeqi if you prefer to pipe it through, and get back a Matrix:
tData
|> Matrix.toRowSeqi
|> Seq.map (fun (i,x) -> (theta' * x) - yVals.[i])
|> DenseMatrix.ofRowSeq
I'm loading a sequence of records into a deedle data frame (from a database table). Is it possible to accumulate (for example sum cumulatively) the values, and get back a data frame? For example there is Series.scanValues but there is no Frame.scanValues. There is Frame.map, but it didn't do what I expected, it left all values as they were.
#if INTERACTIVE
#r #"Fsharp.Charting"
#load #"..\..\Deedle.fsx"
#endif
open FSharp.Charting
open FSharp.Charting.ChartTypes
open Deedle
type SeriesX = {
DataDate:DateTime
Series1:float
Series2:float
Series3:float
}
let rnd = new System.Random()
rnd.NextDouble() - 0.5
let data =
[for i in [100..-1..1] ->
{SeriesX.DataDate = DateTime.Now.AddDays(float -i)
SeriesX.Series1 = rnd.NextDouble() - 0.5
SeriesX.Series2 = rnd.NextDouble() - 0.5
SeriesX.Series3 = rnd.NextDouble() - 0.5
}
]
# now comes the deedle frame:
let df = data |> Frame.ofRecords
let df = df.IndexRows<DateTime>("DataDate")
df.["Series1"] |> Chart.Line
df.["Series1"].ScanValues((fun acc x -> acc + x),0.0) |> Chart.Line
let df' = df |> Frame.mapValues (Seq.scan (fun acc x -> acc + x) 0.0)
df'.["Series1"] |> Chart.Line
The last two lines just give me back the original values while I would like to have the accumulated values like in df.["Series1"].Scanvalues for Series1, Series2, and Series3.
For filtering and projection, series provides Where and Select methods
and corresponding Series.map and Series.filter functions (there is
also Series.mapValues and Series.mapKeys if you only want to transform
one aspect).
So you just apply your function to each Series:
let allSum =
df.Columns
|> Series.mapValues(Series.scanValues(fun acc v -> acc + (v :?> float)) 0.0)
|> Frame.ofColumns
and use Frame.ofColumns that to convert the result to the Frame.
Edit:
If you need to select only numerics columns, you can use the Frame.getNumericCols:
let allSum =
df
|> Frame.getNumericCols
|> Series.mapValues(Series.scanValues (+) 0.0)
|> Frame.ofColumns
without an explicit type cast code has become more beautiful :)
There is a Series.scanValues function. You can obtain a series from every column in your data frame like this: frame$column, which gets you a Series.
If you need all the columns at once to do the scan, you could first map each row into a single value (a tuple, for example) and the apply the Series.scanValues to that new column.
basically having this:
[
[1;2;3];
[4;5;7];
[8;9;0];
]
I would like to get this (read vertically/ turn 90 degrees):
[
[1;4;8];
[2;5;9];
[3;7;0];
]
anybody knows an easy way of doing this in f# ?
I would do it by converting to arrays -
let arr = input |> List.map (List.toArray) |> List.toArray //a array of arrays
let out = Array2D.create size1 size2 (fun x y -> arr.[y].[x])
What you need is called matrix transposition.
PowerPack
The simplest way is using FSharp.PowerPack; Microsoft.FSharp.Math.Matrix module has Transpose method.
Simple algorithm
If you prefer your own solution, here's the one that demonstrates a good combination of short code and executing efficiency:
let rec transpose = function
| (_::_)::_ as M -> List.map List.head M :: transpose (List.map List.tail M)
| _ -> []
// use
[[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
|> transpose
|> printfn "%A"
In-place matrix transposition
Yet another approach is in-place matrix transposition. It has complexity of O(n), but requires mutable data.