From 6fa950a4f852bd45b81c941877732ec516dcc673 Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 16 Apr 2022 13:53:16 +0800 Subject: [PATCH] ref #65, new formula functions: SKEW.P and SLOPE, remove no-required format default --- calc.go | 74 +++++++++++++++++++++++++++++++-------- calc_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ chart.go | 7 +--- picture.go | 5 --- shape.go | 7 +--- table.go | 5 +-- 6 files changed, 160 insertions(+), 35 deletions(-) diff --git a/calc.go b/calc.go index a90bbc3e..03d467b8 100644 --- a/calc.go +++ b/calc.go @@ -640,7 +640,9 @@ type formulaFuncs struct { // SIN // SINH // SKEW +// SKEW.P // SLN +// SLOPE // SMALL // SQRT // SQRTPI @@ -8860,14 +8862,20 @@ func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg { return newNumberFormulaArg(min) } -// pearsonProduct is an implementation of the formula functions PEARSON and -// RSQ. +// pearsonProduct is an implementation of the formula functions PEARSON, RSQ +// and SLOPE. func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) } - array1 := argsList.Front().Value.(formulaArg).ToList() - array2 := argsList.Back().Value.(formulaArg).ToList() + var array1, array2 []formulaArg + if name == "SLOPE" { + array1 = argsList.Back().Value.(formulaArg).ToList() + array2 = argsList.Front().Value.(formulaArg).ToList() + } else { + array1 = argsList.Front().Value.(formulaArg).ToList() + array2 = argsList.Back().Value.(formulaArg).ToList() + } if len(array1) != len(array2) { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } @@ -8898,7 +8906,10 @@ func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formula if name == "RSQ" { return newNumberFormulaArg(math.Pow(sum/math.Sqrt(deltaX*deltaY), 2)) } - return newNumberFormulaArg(sum / math.Sqrt(deltaX*deltaY)) + if name == "PEARSON" { + return newNumberFormulaArg(sum / math.Sqrt(deltaX*deltaY)) + } + return newNumberFormulaArg(sum / deltaX) } // PEARSON function calculates the Pearson Product-Moment Correlation @@ -9268,16 +9279,19 @@ func (fn *formulaFuncs) RSQ(argsList *list.List) formulaArg { return fn.pearsonProduct("RSQ", argsList) } -// SKEW function calculates the skewness of the distribution of a supplied set -// of values. The syntax of the function is: -// -// SKEW(number1,[number2],...) -// -func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg { +// skew is an implementation of the formula functions SKEW and SKEW.P. +func (fn *formulaFuncs) skew(name string, argsList *list.List) formulaArg { if argsList.Len() < 1 { - return newErrorFormulaArg(formulaErrorVALUE, "SKEW requires at least 1 argument") + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + mean := fn.AVERAGE(argsList) + var stdDev formulaArg + var count, summer float64 + if name == "SKEW" { + stdDev = fn.STDEV(argsList) + } else { + stdDev = fn.STDEVP(argsList) } - mean, stdDev, count, summer := fn.AVERAGE(argsList), fn.STDEV(argsList), 0.0, 0.0 for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { @@ -9300,11 +9314,43 @@ func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg { } } if count > 2 { - return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2)))) + if name == "SKEW" { + return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2)))) + } + return newNumberFormulaArg(summer / count) } return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) } +// SKEW function calculates the skewness of the distribution of a supplied set +// of values. The syntax of the function is: +// +// SKEW(number1,[number2],...) +// +func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg { + return fn.skew("SKEW", argsList) +} + +// SKEWdotP function calculates the skewness of the distribution of a supplied +// set of values. The syntax of the function is: +// +// SKEW.P(number1,[number2],...) +// +func (fn *formulaFuncs) SKEWdotP(argsList *list.List) formulaArg { + return fn.skew("SKEW.P", argsList) +} + +// SLOPE returns the slope of the linear regression line through data points in +// known_y's and known_x's. The slope is the vertical distance divided by the +// horizontal distance between any two points on the line, which is the rate +// of change along the regression line. The syntax of the function is: +// +// SLOPE(known_y's,known_x's) +// +func (fn *formulaFuncs) SLOPE(argsList *list.List) formulaArg { + return fn.pearsonProduct("SLOPE", argsList) +} + // SMALL function returns the k'th smallest value from an array of numeric // values. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index a7f88b0e..6cae4a3f 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1157,6 +1157,12 @@ func TestCalcCellValue(t *testing.T) { "=SKEW(1,2,3,4,3)": "-0.404796008910937", "=SKEW(A1:B2)": "0", "=SKEW(A1:D3)": "0", + // SKEW.P + "=SKEW.P(1,2,3,4,3)": "-0.27154541788364", + "=SKEW.P(A1:B2)": "0", + "=SKEW.P(A1:D3)": "0", + // SLOPE + "=SLOPE(A1:A4,B1:B4)": "1", // SMALL "=SMALL(A1:A5,1)": "0", "=SMALL(A1:B5,2)": "1", @@ -3063,6 +3069,14 @@ func TestCalcCellValue(t *testing.T) { "=SKEW()": "SKEW requires at least 1 argument", "=SKEW(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=SKEW(0)": "#DIV/0!", + // SKEW.P + "=SKEW.P()": "SKEW.P requires at least 1 argument", + "=SKEW.P(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=SKEW.P(0)": "#DIV/0!", + // SLOPE + "=SLOPE()": "SLOPE requires 2 arguments", + "=SLOPE(A1:A2,B1:B1)": "#N/A", + "=SLOPE(A4,A4)": "#DIV/0!", // SMALL "=SMALL()": "SMALL requires 2 arguments", "=SMALL(A1:A5,0)": "k should be > 0", @@ -4968,6 +4982,89 @@ func TestCalcMODE(t *testing.T) { } } +func TestCalcPEARSON(t *testing.T) { + cellData := [][]interface{}{ + {"x", "y"}, + {1, 10.11}, + {2, 22.9}, + {2, 27.61}, + {3, 27.61}, + {4, 11.15}, + {5, 31.08}, + {6, 37.9}, + {7, 33.49}, + {8, 21.05}, + {9, 27.01}, + {10, 45.78}, + {11, 31.32}, + {12, 50.57}, + {13, 45.48}, + {14, 40.94}, + {15, 53.76}, + {16, 36.18}, + {17, 49.77}, + {18, 55.66}, + {19, 63.83}, + {20, 63.6}, + } + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=PEARSON(A2:A22,B2:B22)": "0.864129542184994", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) + result, err := f.CalcCellValue("Sheet1", "C1") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } +} + +func TestCalcRSQ(t *testing.T) { + cellData := [][]interface{}{ + {"known_y's", "known_x's"}, + {2, 22.9}, + {7, 33.49}, + {8, 34.5}, + {3, 27.61}, + {4, 19.5}, + {1, 10.11}, + {6, 37.9}, + {5, 31.08}, + } + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=RSQ(A2:A9,B2:B9)": "0.711666290486784", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) + result, err := f.CalcCellValue("Sheet1", "C1") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } +} + +func TestCalcSLOP(t *testing.T) { + cellData := [][]interface{}{ + {"known_x's", "known_y's"}, + {1, 3}, + {2, 7}, + {3, 17}, + {4, 20}, + {5, 20}, + {6, 27}, + } + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=SLOPE(A2:A7,B2:B7)": "0.200826446280992", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) + result, err := f.CalcCellValue("Sheet1", "C1") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } +} + func TestCalcSHEET(t *testing.T) { f := NewFile() f.NewSheet("Sheet2") diff --git a/chart.go b/chart.go index 8f521fa0..f740a2b2 100644 --- a/chart.go +++ b/chart.go @@ -479,16 +479,11 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) { }, Format: formatPicture{ FPrintsWithSheet: true, - FLocksWithSheet: false, - NoChangeAspect: false, - OffsetX: 0, - OffsetY: 0, XScale: 1.0, YScale: 1.0, }, Legend: formatChartLegend{ - Position: "bottom", - ShowLegendKey: false, + Position: "bottom", }, Title: formatChartTitle{ Name: " ", diff --git a/picture.go b/picture.go index 515f15f9..919262c9 100644 --- a/picture.go +++ b/picture.go @@ -31,11 +31,6 @@ import ( func parseFormatPictureSet(formatSet string) (*formatPicture, error) { format := formatPicture{ FPrintsWithSheet: true, - FLocksWithSheet: false, - NoChangeAspect: false, - Autofit: false, - OffsetX: 0, - OffsetY: 0, XScale: 1.0, YScale: 1.0, } diff --git a/shape.go b/shape.go index 8aefeea8..db768673 100644 --- a/shape.go +++ b/shape.go @@ -25,15 +25,10 @@ func parseFormatShapeSet(formatSet string) (*formatShape, error) { Height: 160, Format: formatPicture{ FPrintsWithSheet: true, - FLocksWithSheet: false, - NoChangeAspect: false, - OffsetX: 0, - OffsetY: 0, XScale: 1.0, YScale: 1.0, }, - Line: formatLine{Width: 1}, - Macro: "", + Line: formatLine{Width: 1}, } err := json.Unmarshal([]byte(formatSet), &format) return &format, err diff --git a/table.go b/table.go index 0311a8ed..b01c1cb7 100644 --- a/table.go +++ b/table.go @@ -23,10 +23,7 @@ import ( // parseFormatTableSet provides a function to parse the format settings of the // table with default value. func parseFormatTableSet(formatSet string) (*formatTable, error) { - format := formatTable{ - TableStyle: "", - ShowRowStripes: true, - } + format := formatTable{ShowRowStripes: true} err := json.Unmarshal(parseFormatSet(formatSet), &format) return &format, err }