From d13fa38d6851de146586f8c65cf9baad335f5bd2 Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Thu, 13 Feb 2025 19:37:01 +0800 Subject: [PATCH] POI 62373, Support for FREQUENCY function --- main/SS/Formula/Eval/FunctionEval.cs | 2 +- main/SS/Formula/Functions/Frequency.cs | 104 +++++++++++++++++ .../SS/Formula/Functions/TestFrequency.cs | 108 ++++++++++++++++++ 3 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 main/SS/Formula/Functions/Frequency.cs create mode 100644 testcases/main/SS/Formula/Functions/TestFrequency.cs diff --git a/main/SS/Formula/Eval/FunctionEval.cs b/main/SS/Formula/Eval/FunctionEval.cs index abcc9607d..6c79df50b 100644 --- a/main/SS/Formula/Eval/FunctionEval.cs +++ b/main/SS/Formula/Eval/FunctionEval.cs @@ -334,7 +334,7 @@ private static Function[] ProduceFunctions() retval[247] = new NotImplementedFunction("DB"); // DB retval[248] = new NotImplementedFunction("PAUSE"); // PAUSE retval[250] = new NotImplementedFunction("RESUME"); // RESUME - retval[252] = new NotImplementedFunction("FREQUENCY"); // FREQUENCY + retval[252] = Frequency.Instance; // FREQUENCY retval[253] = new NotImplementedFunction("AddTOOLBAR"); // AddTOOLBAR retval[254] = new NotImplementedFunction("DELETETOOLBAR"); // DELETETOOLBAR retval[FunctionID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction diff --git a/main/SS/Formula/Functions/Frequency.cs b/main/SS/Formula/Functions/Frequency.cs new file mode 100644 index 000000000..89bcf7210 --- /dev/null +++ b/main/SS/Formula/Functions/Frequency.cs @@ -0,0 +1,104 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NPOI.SS.Formula.Functions +{ + using NPOI.SS.Formula; + using NPOI.SS.Formula.Eval; + using NPOI.Util; + using NPOI.Util.ArrayExtensions; + + + /// + /// + /// Implementation of Excel 'Analysis ToolPak' function FREQUENCY()
+ /// Returns a frequency distribution as a vertical array + ///
+ /// + /// Syntax
+ /// FREQUENCY(data_array, bins_array) + ///
+ /// + /// data_array Required. An array of or reference to a Set of values for which you want to count frequencies. + /// If data_array contains no values, FREQUENCY returns an array of zeros.
+ /// bins_array Required. An array of or reference to intervals into which you want to group the values in data_array. + /// If bins_array contains no values, FREQUENCY returns the number of elements in data_array.
+ ///
+ ///
+ public class Frequency : Fixed2ArgFunction + { + public static Function Instance = new Frequency(); + + private Frequency() + { + // enforce singleton + } + public override ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) + { + MatrixFunction.MutableValueCollector collector = new MatrixFunction.MutableValueCollector(false, false); + + double[] values; + double[] bins; + try + { + values = collector.collectValues(arg0); + bins = collector.collectValues(arg1); + } + catch(EvaluationException e) + { + return e.GetErrorEval(); + } + + // can bins be not sorted? + //bins = Arrays.stream(bins).sorted().distinct().ToArray(); + + int[] histogram = Histogram(values, bins); + + NumberEval[] result = new NumberEval[histogram.Length]; // Arrays.Stream(histogram).boxed().map(NumberEval::new).ToArray(NumberEval[]::new); + for(var i = 0; i= 0 ? idx + 1 : -idx; + } + + public static int[] Histogram(double[] values, double[] bins) + { + int[] histogram = new int[bins.Length + 1]; + foreach(double val in values) + { + histogram[FindBin(val, bins) - 1]++; + } + return histogram; + } + } +} + diff --git a/testcases/main/SS/Formula/Functions/TestFrequency.cs b/testcases/main/SS/Formula/Functions/TestFrequency.cs new file mode 100644 index 000000000..b78131d96 --- /dev/null +++ b/testcases/main/SS/Formula/Functions/TestFrequency.cs @@ -0,0 +1,108 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NPOI.SS.Formula.Functions +{ + using NPOI.HSSF.UserModel; + using NPOI.SS.UserModel; + using NPOI.SS.Util; + using NPOI.Util; + using NPOI.Util.ArrayExtensions; + using NUnit.Framework; + using static NPOI.SS.Formula.Functions.Frequency; + + /// + /// Testcase for the function FREQUENCY(data, bins) + /// + /// + /// @author Yegor Kozlov + /// + [TestFixture] + public class TestFrequency + { + + [Test] + public void TestHistogram() + { + + Assert.IsTrue(Arrays.Equals(new int[] { 3, 2, 2, 0, 1, 1 }, + Histogram( + new double[] { 11, 12, 13, 21, 29, 36, 40, 58, 69 }, + new double[] { 20, 30, 40, 50, 60 }) + )); + + Assert.IsTrue(Arrays.Equals(new int[] { 1, 1, 1, 1, 1, 0 }, + Histogram( + new double[] { 20, 30, 40, 50, 60 }, + new double[] { 20, 30, 40, 50, 60 }) + + )); + + Assert.IsTrue(Arrays.Equals(new int[] { 2, 3 }, + Histogram( + new double[] { 20, 30, 40, 50, 60 }, + new double[] { 30 }) + + )); + } + + [Test] + public void TestEvaluate() + { + IWorkbook wb = new HSSFWorkbook(); + IFormulaEvaluator evaluator = wb.GetCreationHelper().CreateFormulaEvaluator(); + + int[] data = {1, 1, 2, 3, 4, 4, 5, 7, 8, 9, 9, 11, 3, 5, 8}; + int[] bins = {3, 6, 9}; + ISheet sheet = wb.CreateSheet(); + IRow dataRow = sheet.CreateRow(0); // A1:O1 + for(int i = 0; i < data.Length; i++) + { + dataRow.CreateCell(i).SetCellValue(data[i]); + } + IRow binsRow = sheet.CreateRow(1); + for(int i = 0; i < bins.Length; i++) + { + // A2:C2 + binsRow.CreateCell(i).SetCellValue(bins[i]); + } + IRow fmlaRow = sheet.CreateRow(2); + ICellRange arrayFmla = sheet.SetArrayFormula("FREQUENCY(A1:O1,A2:C2)", CellRangeAddress.ValueOf("A3:A6")); + ICell b3 = fmlaRow.CreateCell(1); // B3 + b3.SetCellFormula("COUNT(FREQUENCY(A1:O1,A2:C2))"); // frequency returns a vertical array of bins+1 + + ICell c3 = fmlaRow.CreateCell(2); + c3.SetCellFormula("SUM(FREQUENCY(A1:O1,A2:C2))"); // sum of the frequency bins should add up to the number of data values + + Assert.AreEqual(5, (int) evaluator.Evaluate(arrayFmla.FlattenedCells[0]).NumberValue); + Assert.AreEqual(4, (int) evaluator.Evaluate(arrayFmla.FlattenedCells[1]).NumberValue); + Assert.AreEqual(5, (int) evaluator.Evaluate(arrayFmla.FlattenedCells[2]).NumberValue); + Assert.AreEqual(1, (int) evaluator.Evaluate(arrayFmla.FlattenedCells[3]).NumberValue); + + Assert.AreEqual(4, (int) evaluator.Evaluate(b3).NumberValue); + Assert.AreEqual(15, (int) evaluator.Evaluate(c3).NumberValue); + + } + } +} +