diff --git a/core/api/core.api b/core/api/core.api index 71f9b264e8..452611ed6c 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -5399,6 +5399,9 @@ public abstract interface class org/jetbrains/kotlinx/dataframe/documentation/Do public abstract interface class org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls$NanAndNa { } +public abstract interface class org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls$Sum { +} + public abstract interface class org/jetbrains/kotlinx/dataframe/documentation/DslGrammarTemplateColumnsSelectionDsl { } @@ -5586,6 +5589,7 @@ public final class org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ public final fun getMultipleColumnsHandler ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorMultipleColumnsHandler; public final fun getName ()Ljava/lang/String; public final fun getStatisticsParameters ()Ljava/util/Map; + public fun getValueTypeIfEmpty ()Lkotlin/reflect/KType; public fun indexOfAggregationResultSingleSequence (Lkotlin/sequences/Sequence;Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ValueType;)I public fun init (Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator;)V public fun preprocessAggregation (Lkotlin/sequences/Sequence;Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ValueType;)Lkotlin/Pair; @@ -5609,6 +5613,7 @@ public abstract interface class org/jetbrains/kotlinx/dataframe/impl/aggregation public abstract interface class org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorInputHandler : org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorHandler { public abstract fun calculateValueType (Ljava/util/Set;)Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ValueType; public abstract fun calculateValueType (Lkotlin/sequences/Sequence;)Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ValueType; + public fun getValueTypeIfEmpty ()Lkotlin/reflect/KType; public abstract fun preprocessAggregation (Lkotlin/sequences/Sequence;Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ValueType;)Lkotlin/Pair; } @@ -5660,7 +5665,7 @@ public final class org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/ public final fun getMin ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorOptionSwitch1; public final fun getPercentile ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorOptionSwitch2; public final fun getStd ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorOptionSwitch2; - public final fun getSum ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorOptionSwitch1; + public final fun getSum ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorOptionSwitch2; public final fun max (Z)Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator; public final fun medianCommon (Z)Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator; public final fun medianComparables ()Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator; diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/max.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/max.kt index a65376da48..92436bc951 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/max.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/max.kt @@ -127,7 +127,7 @@ private typealias SetMaxOrNullOperationArg = Nothing * df.weight.max() * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value in this column. * @throws NoSuchElementException if there are no values to compare. */ @@ -159,7 +159,7 @@ public fun > DataColumn.max(skipNaN: Boolean = skipNaNDefa * df.weight.maxOrNull() * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value in this column, or `null` if there are no values to compare. */ public fun > DataColumn.maxOrNull(skipNaN: Boolean = skipNaNDefault): T? = @@ -189,7 +189,7 @@ public fun > DataColumn.maxOrNull(skipNaN: Boolean = skipN * df.name.firstName.maxBy { it.length } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The first element for which [selector] returns the maximum value. * @throws NoSuchElementException if there are no values to compare. @@ -224,7 +224,7 @@ public inline fun ?> DataColumn.maxBy( * df.name.firstName.maxByOrNull { it.length } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The first element for which [selector] returns the maximum value, * or `null` if there are no values to compare. @@ -258,7 +258,7 @@ public inline fun ?> DataColumn.maxByOrNul * df.name.firstName.maxOf { it.length } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The maximum of the values [selector] returns. * @throws NoSuchElementException if there are no values to compare. @@ -293,7 +293,7 @@ public inline fun ?> DataColumn.maxOf( * df.name.firstName.maxOfOrNull { it.length } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The maximum of the values [selector] returns, * or `null` if there are no values to compare. @@ -339,7 +339,7 @@ public fun DataRow<*>.rowMax(): Nothing = error(ROW_MAX) * ``` * * @param [T] The type of the values to compare. Only columns of this type are taken into account. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value of type [T] in this row, or `null` if there are no values to compare. */ public inline fun > DataRow<*>.rowMaxOfOrNull(skipNaN: Boolean = skipNaNDefault): T? = @@ -371,7 +371,7 @@ public inline fun > DataRow<*>.rowMaxOfOrNull(skipNaN: * ``` * * @param [T] The type of the values to compare. Only columns of this type are taken into account. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value of type [T] in this row. * @throws NoSuchElementException if there are no values to compare. */ @@ -407,7 +407,7 @@ public inline fun > DataRow<*>.rowMaxOf(skipNaN: Boole * df.max() * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A single [DataRow] with the maximum of each suitable column of this [DataFrame]. */ @Refine @@ -444,7 +444,7 @@ public fun DataFrame.max(skipNaN: Boolean = skipNaNDefault): DataRow = * df.maxFor(skipNaN = true) { age into "maxAge" and (weight into "maxWeight") } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns of this [DataFrame] * to compute the maximum of. * @return A single [DataRow] with the maximum of each selected column. @@ -482,7 +482,7 @@ public fun ?> DataFrame.maxFor( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A single [DataRow] with the maximum of each selected column. */ @Refine @@ -531,7 +531,7 @@ public fun ?> DataFrame.maxFor( * df.max { age and weight } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns of this [DataFrame] * to compute the maximum of. * @return The largest value among all the values in the selected columns. @@ -570,7 +570,7 @@ public fun ?> DataFrame.max( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value among all the values in the selected columns. * @throws NoSuchElementException if there are no values to compare. */ @@ -620,7 +620,7 @@ public fun ?> DataFrame.max( * df.maxOrNull { age and weight } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns of this [DataFrame] * to compute the maximum of. * @return The largest value among all the values in the selected columns, @@ -661,7 +661,7 @@ public fun ?> DataFrame.maxOrNull( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The largest value among all the values in the selected columns, * or `null` if there are no values to compare. */ @@ -710,7 +710,7 @@ public fun ?> DataFrame.maxOrNull( * df.maxOf { (weight ?: 0) / age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The maximum of the values [expression] returns. * @throws NoSuchElementException if there are no values to compare. @@ -748,7 +748,7 @@ public inline fun ?> DataFrame.maxOf( * df.maxOfOrNull { (weight ?: 0) / age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The maximum of the values [expression] returns, * or `null` if there are no values to compare. @@ -788,7 +788,7 @@ public inline fun ?> DataFrame.maxOfOrNull * df.maxBy { (weight ?: 0) / age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The first [DataRow] for which [expression] returns the maximum value. * @throws NoSuchElementException if there are no values to compare. @@ -826,7 +826,7 @@ public inline fun ?> DataFrame.maxBy( * ``` * * @param [column] The name of the column of this [DataFrame] to compare the rows by. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The first [DataRow] with the largest value in the given column. * @throws NoSuchElementException if there are no values to compare. */ @@ -873,7 +873,7 @@ public inline fun ?> DataFrame.maxBy( * df.maxByOrNull { age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The first [DataRow] for which [expression] returns the maximum value, * or `null` if there are no values to compare. @@ -910,7 +910,7 @@ public inline fun ?> DataFrame.maxByOrNull * ``` * * @param [column] The name of the column of this [DataFrame] to compare the rows by. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return The first [DataRow] with the largest value in the given column, * or `null` if there are no values to compare. */ @@ -965,7 +965,7 @@ public inline fun ?> DataFrame.maxByOrNull * df.groupBy { city }.max() * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and the maximum of each suitable column per group. */ @Refine @@ -1007,7 +1007,7 @@ public fun Grouped.max(skipNaN: Boolean = skipNaNDefault): DataFrame = * df.groupBy { city }.maxFor { age and weight } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the maximum of. * @return A new [DataFrame] with the group keys and the maximum of each selected column per group. @@ -1052,7 +1052,7 @@ public fun ?> Grouped.maxFor( * ``` * * @param [columns] The names of the columns to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and the maximum of each selected column per group. */ @Refine @@ -1112,7 +1112,7 @@ public fun ?> Grouped.maxFor( * @param [name] The name of the resulting column. * If `null` (the default), the name of the selected column is used if exactly one column * is selected, and `"max"` otherwise. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the maximum of. * @return A new [DataFrame] with the group keys and a single maximum per group. */ @@ -1163,7 +1163,7 @@ public fun ?> Grouped.max( * @param [name] The name of the resulting column. * If `null` (the default), the name of the selected column is used if exactly one column * is selected, and `"max"` otherwise. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and a single maximum per group. */ @Refine @@ -1222,7 +1222,7 @@ public fun ?> Grouped.max( * ``` * * @param [name] The name of the resulting column. If `null` (the default), `"max"` is used. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return A new [DataFrame] with the group keys and a single maximum per group. */ @@ -1261,7 +1261,7 @@ public inline fun ?> Grouped.maxOf( * df.groupBy { city }.maxBy { age }.concat() * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to compute the value to compare for each row. * @return A [ReducedGroupBy] with, for each group, the first row * for which [rowExpression] returns the maximum value. @@ -1306,7 +1306,7 @@ public inline fun ?> GroupBy.maxBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [ReducedGroupBy] with, for each group, the first row * that has the largest value in the given column. */ @@ -1357,7 +1357,7 @@ public inline fun ?> GroupBy.maxBy( * ``` * * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A single [DataRow] with the maximum of each suitable column per [pivot] group. */ public fun Pivot.max(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataRow = @@ -1400,7 +1400,7 @@ public fun Pivot.max(separate: Boolean = false, skipNaN: Boolean = skipNa * ``` * * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the maximum of. * @return A single [DataRow] with the maximum of each selected column per [pivot] group. @@ -1445,7 +1445,7 @@ public fun ?> Pivot.maxFor( * * @param [columns] The names of the columns to compute the maximum of. * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A single [DataRow] with the maximum of each selected column per [pivot] group. */ public fun Pivot.maxFor( @@ -1501,7 +1501,7 @@ public fun ?> Pivot.maxFor( * df.pivot { city }.max { age and weight } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the maximum of. * @return A single [DataRow] with, per [pivot] group, the largest value among all the values * in the selected columns. @@ -1543,7 +1543,7 @@ public fun ?> Pivot.max( * ``` * * @param [columns] The names of the columns to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A single [DataRow] with, per [pivot] group, the largest value among all the values * in the selected columns. */ @@ -1598,7 +1598,7 @@ public fun ?> Pivot.max( * df.pivot { city }.maxOf { (weight ?: 0) / age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A single [DataRow] with, per [pivot] group, the maximum of the expression's results. */ @@ -1637,7 +1637,7 @@ public inline fun ?> Pivot.maxOf( * df.pivot { city }.maxBy { (weight ?: 0) / age }.with { name } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [ReducedPivot] holding, per group, * the first row where the [rowExpression] produced the maximum result. @@ -1684,7 +1684,7 @@ public inline fun ?> Pivot.maxBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [ReducedPivot] holding, per group, the first row with the largest value * in the given column. */ @@ -1736,7 +1736,7 @@ public inline fun ?> Pivot.maxBy( * ``` * * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [DataFrame] with the maximum of each suitable column per group. */ public fun PivotGroupBy.max(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataFrame = @@ -1779,7 +1779,7 @@ public fun PivotGroupBy.max(separate: Boolean = false, skipNaN: Boolean = * ``` * * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the maximum of. * @return A [DataFrame] with the maximum of each selected column per group. @@ -1826,7 +1826,7 @@ public fun ?> PivotGroupBy.maxFor( * * @param [columns] The names of the columns to compute the maximum of. * @include [MaxDocs.SeparateParam] - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [DataFrame] with the maximum of each selected column per group. */ public fun PivotGroupBy.maxFor( @@ -1885,7 +1885,7 @@ public fun ?> PivotGroupBy.maxFor( * df.pivot { city }.groupBy { name.lastName }.max { age and weight } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the maximum of. * @return A [DataFrame] with, per group, the largest value among all the values * in the selected columns. @@ -1930,7 +1930,7 @@ public fun ?> PivotGroupBy.max( * ``` * * @param [columns] The names of the columns to compute the maximum of. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [DataFrame] with, per group, the largest value among all the values * in the selected columns. */ @@ -1986,7 +1986,7 @@ public fun ?> PivotGroupBy.max( * df.pivot { city }.groupBy { name.lastName }.maxOf { (weight ?: 0) / age } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [DataFrame] with, per group, the maximum of the expression's results. */ @@ -2026,7 +2026,7 @@ public inline fun ?> PivotGroupBy.maxOf( * df.pivot { city }.groupBy { name.lastName }.maxBy { (weight ?: 0) / age }.with { name.firstName } * ``` * - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [ReducedPivotGroupBy] holding, per group, * the first row where the [rowExpression] produced the maximum result. @@ -2074,7 +2074,7 @@ public inline fun ?> PivotGroupBy.maxBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MaxDocs.SkipNaNParam] + * @include [MaxDocs.SkipNanParam] * @return A [ReducedPivotGroupBy] holding, per group, the first row with the largest value * in the given column. */ diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/min.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/min.kt index bd0c30baac..bd22e580aa 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/min.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/min.kt @@ -127,7 +127,7 @@ private typealias SetMinOrNullOperationArg = Nothing * df.weight.min() * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value in this column. * @throws NoSuchElementException if there are no values to compare. */ @@ -159,7 +159,7 @@ public fun > DataColumn.min(skipNaN: Boolean = skipNaNDefa * df.weight.minOrNull() * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value in this column, or `null` if there are no values to compare. */ public fun > DataColumn.minOrNull(skipNaN: Boolean = skipNaNDefault): T? = @@ -189,7 +189,7 @@ public fun > DataColumn.minOrNull(skipNaN: Boolean = skipN * df.name.firstName.minBy { it.length } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The first element for which [selector] returns the minimum value. * @throws NoSuchElementException if there are no values to compare. @@ -224,7 +224,7 @@ public inline fun ?> DataColumn.minBy( * df.name.firstName.minByOrNull { it.length } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The first element for which [selector] returns the minimum value, * or `null` if there are no values to compare. @@ -258,7 +258,7 @@ public inline fun ?> DataColumn.minByOrNul * df.name.firstName.minOf { it.length } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The minimum of the values [selector] returns. * @throws NoSuchElementException if there are no values to compare. @@ -293,7 +293,7 @@ public inline fun ?> DataColumn.minOf( * df.name.firstName.minOfOrNull { it.length } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [selector] A function that returns the value to compare for each element of this column. * @return The minimum of the values [selector] returns, * or `null` if there are no values to compare. @@ -339,7 +339,7 @@ public fun DataRow<*>.rowMin(): Nothing = error(ROW_MIN) * ``` * * @param [T] The type of the values to compare. Only columns of this type are taken into account. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value of type [T] in this row, or `null` if there are no values to compare. */ public inline fun > DataRow<*>.rowMinOfOrNull(skipNaN: Boolean = skipNaNDefault): T? = @@ -371,7 +371,7 @@ public inline fun > DataRow<*>.rowMinOfOrNull(skipNaN: * ``` * * @param [T] The type of the values to compare. Only columns of this type are taken into account. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value of type [T] in this row. * @throws NoSuchElementException if there are no values to compare. */ @@ -407,7 +407,7 @@ public inline fun > DataRow<*>.rowMinOf(skipNaN: Boole * df.min() * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A single [DataRow] with the minimum of each suitable column of this [DataFrame]. */ @Refine @@ -444,7 +444,7 @@ public fun DataFrame.min(skipNaN: Boolean = skipNaNDefault): DataRow = * df.minFor(skipNaN = true) { age into "minAge" and (weight into "minWeight") } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns of this [DataFrame] * to compute the minimum of. * @return A single [DataRow] with the minimum of each selected column. @@ -482,7 +482,7 @@ public fun ?> DataFrame.minFor( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A single [DataRow] with the minimum of each selected column. */ @Refine @@ -531,7 +531,7 @@ public fun ?> DataFrame.minFor( * df.min { age and weight } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns of this [DataFrame] * to compute the minimum of. * @return The smallest value among all the values in the selected columns. @@ -570,7 +570,7 @@ public fun ?> DataFrame.min( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value among all the values in the selected columns. * @throws NoSuchElementException if there are no values to compare. */ @@ -620,7 +620,7 @@ public fun ?> DataFrame.min( * df.minOrNull { age and weight } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns of this [DataFrame] * to compute the minimum of. * @return The smallest value among all the values in the selected columns, @@ -661,7 +661,7 @@ public fun ?> DataFrame.minOrNull( * ``` * * @param [columns] The names of the columns of this [DataFrame] to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The smallest value among all the values in the selected columns, * or `null` if there are no values to compare. */ @@ -710,7 +710,7 @@ public fun ?> DataFrame.minOrNull( * df.minOf { (weight ?: 0) / age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The minimum of the values [expression] returns. * @throws NoSuchElementException if there are no values to compare. @@ -748,7 +748,7 @@ public inline fun ?> DataFrame.minOf( * df.minOfOrNull { (weight ?: 0) / age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The minimum of the values [expression] returns, * or `null` if there are no values to compare. @@ -788,7 +788,7 @@ public inline fun ?> DataFrame.minOfOrNull * df.minBy { (weight ?: 0) / age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The first [DataRow] for which [expression] returns the minimum value. * @throws NoSuchElementException if there are no values to compare. @@ -826,7 +826,7 @@ public inline fun ?> DataFrame.minBy( * ``` * * @param [column] The name of the column of this [DataFrame] to compare the rows by. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The first [DataRow] with the smallest value in the given column. * @throws NoSuchElementException if there are no values to compare. */ @@ -873,7 +873,7 @@ public inline fun ?> DataFrame.minBy( * df.minByOrNull { age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return The first [DataRow] for which [expression] returns the minimum value, * or `null` if there are no values to compare. @@ -910,7 +910,7 @@ public inline fun ?> DataFrame.minByOrNull * ``` * * @param [column] The name of the column of this [DataFrame] to compare the rows by. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return The first [DataRow] with the smallest value in the given column, * or `null` if there are no values to compare. */ @@ -965,7 +965,7 @@ public inline fun ?> DataFrame.minByOrNull * df.groupBy { city }.min() * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and the minimum of each suitable column per group. */ @Refine @@ -1007,7 +1007,7 @@ public fun Grouped.min(skipNaN: Boolean = skipNaNDefault): DataFrame = * df.groupBy { city }.minFor { age and weight } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the minimum of. * @return A new [DataFrame] with the group keys and the minimum of each selected column per group. @@ -1052,7 +1052,7 @@ public fun ?> Grouped.minFor( * ``` * * @param [columns] The names of the columns to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and the minimum of each selected column per group. */ @Refine @@ -1112,7 +1112,7 @@ public fun ?> Grouped.minFor( * @param [name] The name of the resulting column. * If `null` (the default), the name of the selected column is used if exactly one column * is selected, and `"min"` otherwise. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the minimum of. * @return A new [DataFrame] with the group keys and a single minimum per group. */ @@ -1163,7 +1163,7 @@ public fun ?> Grouped.min( * @param [name] The name of the resulting column. * If `null` (the default), the name of the selected column is used if exactly one column * is selected, and `"min"` otherwise. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A new [DataFrame] with the group keys and a single minimum per group. */ @Refine @@ -1222,7 +1222,7 @@ public fun ?> Grouped.min( * ``` * * @param [name] The name of the resulting column. If `null` (the default), `"min"` is used. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [expression] The [RowExpression] to compute the value to compare for each row. * @return A new [DataFrame] with the group keys and a single minimum per group. */ @@ -1261,7 +1261,7 @@ public inline fun ?> Grouped.minOf( * df.groupBy { city }.minBy { age }.concat() * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to compute the value to compare for each row. * @return A [ReducedGroupBy] with, for each group, the first row * for which [rowExpression] returns the minimum value. @@ -1306,7 +1306,7 @@ public inline fun ?> GroupBy.minBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [ReducedGroupBy] with, for each group, the first row * that has the smallest value in the given column. */ @@ -1357,7 +1357,7 @@ public inline fun ?> GroupBy.minBy( * ``` * * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A single [DataRow] with the minimum of each suitable column per [pivot] group. */ public fun Pivot.min(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataRow = @@ -1400,7 +1400,7 @@ public fun Pivot.min(separate: Boolean = false, skipNaN: Boolean = skipNa * ``` * * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the minimum of. * @return A single [DataRow] with the minimum of each selected column per [pivot] group. @@ -1445,7 +1445,7 @@ public fun ?> Pivot.minFor( * * @param [columns] The names of the columns to compute the minimum of. * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A single [DataRow] with the minimum of each selected column per [pivot] group. */ public fun Pivot.minFor( @@ -1501,7 +1501,7 @@ public fun ?> Pivot.minFor( * df.pivot { city }.min { age and weight } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the minimum of. * @return A single [DataRow] with, per [pivot] group, the smallest value among all the values * in the selected columns. @@ -1543,7 +1543,7 @@ public fun ?> Pivot.min( * ``` * * @param [columns] The names of the columns to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A single [DataRow] with, per [pivot] group, the smallest value among all the values * in the selected columns. */ @@ -1598,7 +1598,7 @@ public fun ?> Pivot.min( * df.pivot { city }.minOf { (weight ?: 0) / age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A single [DataRow] with, per [pivot] group, the minimum of the expression's results. */ @@ -1637,7 +1637,7 @@ public inline fun ?> Pivot.minOf( * df.pivot { city }.minBy { (weight ?: 0) / age }.with { name } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [ReducedPivot] holding, per group, * the first row where the [rowExpression] produced the minimum result. @@ -1684,7 +1684,7 @@ public inline fun ?> Pivot.minBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [ReducedPivot] holding, per group, the first row with the smallest value * in the given column. */ @@ -1736,7 +1736,7 @@ public inline fun ?> Pivot.minBy( * ``` * * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [DataFrame] with the minimum of each suitable column per group. */ public fun PivotGroupBy.min(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataFrame = @@ -1779,7 +1779,7 @@ public fun PivotGroupBy.min(separate: Boolean = false, skipNaN: Boolean = * ``` * * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsForAggregateSelector] used to select the columns * to compute the minimum of. * @return A [DataFrame] with the minimum of each selected column per group. @@ -1826,7 +1826,7 @@ public fun ?> PivotGroupBy.minFor( * * @param [columns] The names of the columns to compute the minimum of. * @include [MinDocs.SeparateParam] - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [DataFrame] with the minimum of each selected column per group. */ public fun PivotGroupBy.minFor( @@ -1885,7 +1885,7 @@ public fun ?> PivotGroupBy.minFor( * df.pivot { city }.groupBy { name.lastName }.min { age and weight } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [columns] The [ColumnsSelector] used to select the columns to compute the minimum of. * @return A [DataFrame] with, per group, the smallest value among all the values * in the selected columns. @@ -1930,7 +1930,7 @@ public fun ?> PivotGroupBy.min( * ``` * * @param [columns] The names of the columns to compute the minimum of. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [DataFrame] with, per group, the smallest value among all the values * in the selected columns. */ @@ -1986,7 +1986,7 @@ public fun ?> PivotGroupBy.min( * df.pivot { city }.groupBy { name.lastName }.minOf { (weight ?: 0) / age } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [DataFrame] with, per group, the minimum of the expression's results. */ @@ -2026,7 +2026,7 @@ public inline fun ?> PivotGroupBy.minOf( * df.pivot { city }.groupBy { name.lastName }.minBy { (weight ?: 0) / age }.with { name.firstName } * ``` * - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @param [rowExpression] The [RowExpression] to evaluate for each row. * @return A [ReducedPivotGroupBy] holding, per group, * the first row where the [rowExpression] produced the minimum result. @@ -2074,7 +2074,7 @@ public inline fun ?> PivotGroupBy.minBy( * ``` * * @param [column] The name of the column to compare the rows by. - * @include [MinDocs.SkipNaNParam] + * @include [MinDocs.SkipNanParam] * @return A [ReducedPivotGroupBy] holding, per group, the first row with the smallest value * in the given column. */ diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/sum.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/sum.kt index 3be52e2bcc..1266c39431 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/sum.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/sum.kt @@ -13,9 +13,15 @@ import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload import org.jetbrains.kotlinx.dataframe.annotations.Interpretable import org.jetbrains.kotlinx.dataframe.annotations.Refine import org.jetbrains.kotlinx.dataframe.annotations.StringApiInterpretable +import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.columns.ColumnReference import org.jetbrains.kotlinx.dataframe.columns.toColumnSet import org.jetbrains.kotlinx.dataframe.columns.toColumnsSetOf +import org.jetbrains.kotlinx.dataframe.documentation.CommonStatisticsDocs +import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls +import org.jetbrains.kotlinx.dataframe.documentation.ExcludeFromSources +import org.jetbrains.kotlinx.dataframe.documentation.SelectingColumns +import org.jetbrains.kotlinx.dataframe.documentation.UnifyingNumbers import org.jetbrains.kotlinx.dataframe.impl.aggregation.aggregators.Aggregators import org.jetbrains.kotlinx.dataframe.impl.aggregation.modes.aggregateAll import org.jetbrains.kotlinx.dataframe.impl.aggregation.modes.aggregateFor @@ -33,73 +39,868 @@ import kotlin.reflect.KType import kotlin.reflect.full.withNullability import kotlin.reflect.typeOf -/* TODO KDocs - * Calculating the sum is supported for all primitive number types. - * Nulls are filtered out. - * The return type is always the same as the input type (never null), except for `Byte` and `Short`, - * which are converted to `Int`. - * Empty input will result in 0 in the supplied number type. - * For mixed primitive number types, the aggregator unifies the numbers before calculating the sum. +// region docs + +/** + * {@comment + * The Sum Operation KDoc-topic; it also holds all common `sum` KDoc-snippets. + * Link to it with `{@include [SumDocsLink]}`. + * } + * + * ## The Sum Operation + * + * Computes the [sum](https://en.wikipedia.org/wiki/Summation) of values. + * + * @include [SupportedTypesSnippet] + * + * ### Sum Modes + * + * Depending on what exactly you want the sum of, there are several modes. + * They are shown here for [DataFrame], but they exist for the other receivers too: + * + * - [`sum`][DataFrame.sum]`()` — the sum of each suitable column separately. + * - [`sum`][DataFrame.sum]` { columns }` — a single sum of all values in all selected columns. + * - [`sumFor`][DataFrame.sumFor]` { columns }` — the sum of each selected column separately. + * - [`sumOf`][DataFrame.sumOf]` { expression }` — the sum of the values that the given expression + * returns for each row. + * + * Related operations: + * - [`mean`][DataFrame.mean] — the sum divided by the number of values. + * - [`cumSum`][DataFrame.cumSum] — the running sum: each value plus all the values before it. + * + * For more information: {@include [DocumentationUrls.Sum]} + * + * For more information about [unifying numbers][UnifyingNumbers]: + * @include [DocumentationUrls.NumberUnification] + * + * See all summary statistics: + * @include [DocumentationUrls.Statistics] */ +internal interface SumDocs : CommonStatisticsDocs { + + /** + * {@comment Note about which values are supported, how `null` and `NaN` values are treated, + * and what type the result has. KDoc-snippet.} + * + * All primitive number types are supported: [Byte], [Short], [Int], [Long], [Float], and [Double]. + * "Mixed" [Number] input is supported too, as long as it consists solely of those primitive numbers; + * its values are then first converted to their common type using + * [UnifiedNumberTypeOptions.PRIMITIVES_ONLY][org.jetbrains.kotlinx.dataframe.impl.UnifiedNumberTypeOptions.PRIMITIVES_ONLY], + * see [number unification][UnifyingNumbers]. + * Big numbers ([`BigInteger`][java.math.BigInteger], [`BigDecimal`][java.math.BigDecimal]) are not + * supported; [`convert`][DataFrame.convert] them to a primitive number type first. + * + * @include [CommonStatisticsDocs.NullHandlingSnippet] + * {@get [NAN_NOTE] {@include [CommonStatisticsDocs.NaNHandlingSnippet]}} + * + * The result is never `null` and has the same type as the input, except for [Byte] and [Short], + * which sum to [Int], and "mixed" [Number] input, which sums to the common type of its values. + * + * For more information about the resulting types: + * @include [DocumentationUrls.Sum.TypeConversion] + */ + @ExcludeFromSources + interface SupportedTypesSnippet { + + // The note about `NaN` handling. Filled in by default; must be set to nothing for the + // overloads of a fixed integer type, as those have no `skipNaN` parameter to refer to. + typealias NAN_NOTE = Nothing + } + + /** + * {@comment Note about the behavior on empty input for the modes with a single result. KDoc-snippet.} + * + * When there is nothing to sum, for instance, when the input is empty or contains only `null` values, + * the result is `0` in the result type + */ + @ExcludeFromSources + typealias ZeroOnEmptySnippet = Nothing + + /** + * {@comment Note about the behavior on empty input for the modes + * with a single result with comment about defaulting to Double. KDoc-snippet.} + * + * @include [ZeroOnEmptySnippet] (defaulting to [Double] if there is no number type to speak of) + */ + @ExcludeFromSources + typealias ZeroOnEmptyDefaultDoubleSnippet = Nothing + + /** + * {@comment Note about the behavior on empty input for the modes with multiple results. KDoc-snippet.} + * + * Result cells for which there is nothing to sum + * (for instance, because the input was empty or contained only `null` values) + * simply become `0` in the result type (defaulting to [Double] if there is no number type to speak of). + * + * For more information about the resulting types: + * @include [DocumentationUrls.Sum.TypeConversion] + */ + @ExcludeFromSources + typealias ZeroCellOnEmptySnippet = Nothing + + /** + * {@comment Note about the behavior on empty input for the Pivot functions. KDoc-snippet.} + * + * Result cells for which there exists a group, but there is nothing to sum + * (for instance, because the group was empty or contained only `null` values) + * simply become `0` in the result type (defaulting to [Double] if there is no number type to speak of). + * + * For more information about the resulting types: + * @include [DocumentationUrls.Sum.TypeConversion] + * + * For empty pivot intersections, `null` or the [set default][PivotGroupBy.default] are used. + */ + @ExcludeFromSources + typealias ZeroCellOnEmptyPivotSnippet = Nothing + + /** + * {@comment Note about which columns the no-argument `sum` modes take into account. KDoc-snippet.} + * + * All columns of a primitive number type (and all "mixed" [Number] columns) are taken into account; + * the other columns are simply left out of the result. + * + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + */ + @ExcludeFromSources + typealias AllSuitableColumnsSnippet = Nothing + + /** + * {@comment The documentation website links all [Grouped] `sum` KDocs end with. KDoc-snippet.} + * + * For more information: {@include [DocumentationUrls.GroupByStatistics]}, and + * {@include [DocumentationUrls.GroupByAggregationStatistics]} + */ + @ExcludeFromSources + typealias GroupByUrlsSnippet = Nothing + + /** + * {@comment The documentation website links all [Pivot] and [PivotGroupBy] `sum` KDocs end with. + * KDoc-snippet.} + * + * For more information: {@include [DocumentationUrls.PivotStatistics]}, and + * {@include [DocumentationUrls.PivotAggregationStatistics]} + */ + @ExcludeFromSources + typealias PivotUrlsSnippet = Nothing + + /** + * @comment Version of [SelectingColumns] with correctly filled in examples + * @include [SelectingColumns] {@include [SetSumOperationArg]} + */ + typealias SumSelectingOptions = Nothing + + /** + * @comment Version of [SelectingColumns] with correctly filled in examples + * @include [SelectingColumns] {@include [SetSumForOperationArg]} + */ + typealias SumForSelectingOptions = Nothing + + /** + * @include [SumDocs.ZeroOnEmptyDefaultDoubleSnippet] + * + * See also: + * - [`sumOf`][DataColumn.sumOf] — the sum of the values an expression returns for each element. + * - [`mean`][DataColumn.mean] — the sum divided by the number of values. + * - [`cumSum`][DataColumn.cumSum] — the running sum of the values in this column. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: + * @include [DocumentationUrls.Sum] + * + * ### Example + */ + @ExcludeFromSources + typealias DataColumnSumSnippet = Nothing + + /** + * {@comment The parts all [DataColumn.sumOf] overloads have in common. KDoc-snippet.} + * + * The result of [expression\] is treated as the 'input' of this operation. + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroOnEmptyDefaultDoubleSnippet] + * + * See also: + * - [`sum`][DataColumn.sum] — the sum of the values in this column itself. + * - [`meanOf`][DataColumn.meanOf] — the sum of those values divided by the number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: + * @include [DocumentationUrls.Sum] + * + * ### Example + * $[EXAMPLE] + * + * @param [expression\] A function that returns the value to sum for each element of this column. + */ + @ExcludeFromSources + interface DataColumnSumOfSnippet { + + // The example to render for this sumOf overload + typealias EXAMPLE = Nothing + } + + /** + * @comment The parts all [DataRow.rowSumOf] overloads have in common. KDoc-snippet. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroOnEmptySnippet] + * + * See also: + * - [`rowSum`][DataRow.rowSum] — the sum of all the numbers in this row, of any number type. + * - [`sum`][DataFrame.sum] — the sum of the values in specific columns of a [DataFrame]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: + * @include [DocumentationUrls.RowStatistics] + * + * ### Example + */ + @ExcludeFromSources + typealias RowSumOfSnippet = Nothing + + /** + * @comment [RowSumOfSnippet] plus the parameters that all reified [DataRow.rowSumOf] overloads + * have in common. KDoc-snippet. + * + * @include [SumDocs.RowSumOfSnippet] + * $[EXAMPLE] + * @param [T\] The type of the values to sum. Only columns of this type are taken into account. + * @param [_kClass\] Technical parameter to distinguish this overload from the others; + * you never need to supply it. + */ + @ExcludeFromSources + interface ReifiedRowSumOfSnippet { + + // The example to render for this rowSumOf overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all [DataFrame.sumFor] overloads have in common. KDoc-snippet.} + * + * Returns the sum of the values of each selected column of this [DataFrame] separately. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * $[NOTE] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumForSelectingOptions]. + * + * See also: + * - [`sum`][DataFrame.sum]`()` — the same, but for all suitable columns at once. + * - [`sum`][DataFrame.sum]` { columns }` — a single sum of all values in the selected columns. + * - [`meanFor`][DataFrame.meanFor] — the sum of each selected column divided by its number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: {@include [DocumentationUrls.Sum]} + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface DataFrameSumForSnippet { + + // The note about the aggregate columns selector; can be omitted + typealias NOTE = Nothing + + // The example to render for this sumFor overload + typealias EXAMPLE = Nothing + } + + /** + * @comment The parts all column-selecting [DataFrame.sum] overloads have in common. + * KDoc-snippet. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroOnEmptyDefaultDoubleSnippet] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See also: + * - [`sumFor`][DataFrame.sumFor] — the sum of each selected column separately. + * - [`sumOf`][DataFrame.sumOf] — the sum of the values a row expression returns for each row. + * $[SEE_ALSO_TAIL] + * + * For more information: {@include [DocumentationUrls.Sum]} + * + * $[COLUMNS_API] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface DataFrameSumSnippet { + + // The remaining "See also" bullets of this sum overload. + typealias SEE_ALSO_TAIL = Nothing + + // How this overload selects its columns: the Columns Selection DSL or column names + typealias COLUMNS_API = Nothing + + // The example to render for this sum overload + typealias EXAMPLE = Nothing + } + + /** + * @comment The parts all [DataFrame.sumOf] overloads have in common. + * KDoc-snippet. + * + * @include [SumDocs.RowExpressionSnippet] + * + * The result of the [expression\] is considered the 'input' of this operation. + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroOnEmptySnippet] + * + * See also: + * - [`sum`][DataFrame.sum]` { columns }` — a single sum of all values in the selected columns. + * $[SEE_ALSO_TAIL] + * + * For more information: {@include [DocumentationUrls.Sum]} + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface DataFrameSumOfSnippet { + + // The remaining "See also" bullets of this sumOf overload. + typealias SEE_ALSO_TAIL = Nothing + + // The example to render for this sumOf overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all [Grouped.sumFor] overloads have in common. KDoc-snippet.} + * + * Aggregates this [GroupBy] by computing the sum of the values of + * each selected column separately, per group. + * + * Returns a new [DataFrame] with one row per group, containing the group key columns + * and a column with the sum for each selected column. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * $[NOTE] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumForSelectingOptions]. + * + * See also: + * - [`sum`][Grouped.sum]`()` — the same, but for all suitable columns at once. + * - [`sum`][Grouped.sum]` { columns }` — a single sum of all values in the selected columns, per group. + * - [`meanFor`][Grouped.meanFor] — the sum of each selected column divided by its number of values, + * per group. + * - [`aggregate`][Grouped.aggregate] — the general way to aggregate groups. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.GroupByUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface GroupedSumForSnippet { + + // The note about the aggregate columns selector; can be omitted + typealias NOTE = Nothing + + // The example to render for this sumFor overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all column-selecting [Grouped.sum] overloads have in common. + * KDoc-snippet.} + * + * Aggregates this [GroupBy] by computing a single sum of all the values + * in the selected columns, per group. + * + * Returns a new [DataFrame] with one row per group, containing the group key columns and + * a single column with the sum per group. + * That column is named [name\], or, if [name\] is `null`, after the selected column + * if exactly one column is selected, and `"sum"` otherwise. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumSelectingOptions]. + * + * See also: + * - [`sumFor`][Grouped.sumFor] — the sum of each selected column separately, per group. + * - [`sumOf`][Grouped.sumOf] — the sum of the values a row expression returns + * for each row of a group. + * - [`aggregate`][Grouped.aggregate] — the general way to aggregate groups. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.GroupByUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface GroupedSumSnippet { + + // The example to render for this sum overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all [Pivot.sumFor] overloads have in common. KDoc-snippet.} + * + * Aggregates this [Pivot] by computing the sum of the values of + * each selected column separately, per group. + * + * Returns a single [DataRow] with the [pivot] keys as (nested) columns, containing the sum + * of each selected column of the corresponding group. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * $[NOTE] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumForSelectingOptions], or check out the + * [`Pivot` Grammar][PivotDocs.Grammar]. + * + * See also: + * - [`sum`][Pivot.sum]`()` — the same, but for all suitable columns at once. + * - [`sum`][Pivot.sum]` { columns }` — a single sum of all values in the selected columns, per group. + * - [Pivot aggregation][PivotDocs.Aggregation] — all other ways to aggregate a [Pivot]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface PivotSumForSnippet { + + // The note about the aggregate columns selector; can be omitted + typealias NOTE = Nothing + + // The example to render for this sumFor overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all column-selecting [Pivot.sum] overloads have in common. + * KDoc-snippet.} + * + * Aggregates this [Pivot] by computing a single sum of all the values + * in the selected columns, per group. + * + * Returns a single [DataRow] with the [pivot] keys as (nested) columns, containing the sum of all + * the values in the selected columns of the corresponding group. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumSelectingOptions], or check out the + * [`Pivot` Grammar][PivotDocs.Grammar]. + * + * See also: + * - [`sum`][Pivot.sum]`()` — the sum of each suitable column separately, per group. + * - [`sumFor`][Pivot.sumFor] — the sum of each selected column separately, per group. + * - [Pivot aggregation][PivotDocs.Aggregation] — all other ways to aggregate a [Pivot]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface PivotSumSnippet { + + // The example to render for this sum overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all [PivotGroupBy.sumFor] overloads have in common. KDoc-snippet.} + * + * Aggregates this [PivotGroupBy] by computing the sum of the values of + * each selected column separately, per group. + * + * Returns a [DataFrame] where each cell contains the sum of each selected column + * of the group corresponding to that [pivot] key (column) and [groupBy] key (row). + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * $[NOTE] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumForSelectingOptions], or check out the + * [`PivotGroupBy` Grammar][PivotGroupByDocs.Grammar]. + * + * See also: + * - [`sum`][PivotGroupBy.sum]`()` — the same, but for all suitable columns at once. + * - [`sum`][PivotGroupBy.sum]` { columns }` — a single sum of all values in the selected columns, + * per group. + * - [PivotGroupBy aggregation][PivotGroupByDocs.Aggregation] — all other ways to aggregate + * a [PivotGroupBy]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface PivotGroupBySumForSnippet { + + // The note about the aggregate columns selector; can be omitted + typealias NOTE = Nothing + + // The example to render for this sumFor overload + typealias EXAMPLE = Nothing + } + + /** + * {@comment The parts all column-selecting [PivotGroupBy.sum] overloads have in common. + * KDoc-snippet.} + * + * Aggregates this [PivotGroupBy] by computing a single sum of all the values + * in the selected columns, per group. + * + * Returns a [DataFrame] where each cell contains the sum of all the values in the selected columns + * of the group corresponding to that [pivot] key (column) and [groupBy] key (row). + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsSnippet] + * + * See [Selecting Columns][SumDocs.SumSelectingOptions], or check out the + * [`PivotGroupBy` Grammar][PivotGroupByDocs.Grammar]. + * + * See also: + * - [`sum`][PivotGroupBy.sum]`()` — the sum of each suitable column separately, per group. + * - [`sumFor`][PivotGroupBy.sumFor] — the sum of each selected column separately, per group. + * - [PivotGroupBy aggregation][PivotGroupByDocs.Aggregation] — all other ways to aggregate + * a [PivotGroupBy]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * $[EXAMPLE] + */ + @ExcludeFromSources + interface PivotGroupBySumSnippet { + + // The example to render for this sum overload + typealias EXAMPLE = Nothing + } + + /** + * @comment The `columns` parameter of the [ColumnsSelector] overloads. KDoc-snippet. + * + * @param [columns\] The [ColumnsSelector] used to select the columns to compute the sum of. + */ + @ExcludeFromSources + typealias ColumnsSelectorParam = Nothing + + /** + * @comment The `columns` parameter of the [ColumnsForAggregateSelector] overloads. KDoc-snippet. + * + * @param [columns\] The [ColumnsForAggregateSelector] used to select the columns + * to compute the sum of. + */ + @ExcludeFromSources + typealias AggregateColumnsSelectorParam = Nothing + + /** + * @comment The `columns` parameter of the [String] overloads. KDoc-snippet. + * + * @param [columns\] The names of the columns to compute the sum of. + * These must be primitive number columns, else an [IllegalArgumentException] is thrown. + */ + @ExcludeFromSources + typealias ColumnNamesParam = Nothing + + /** + * @comment The `expression` parameter of the `sumOf` overloads. KDoc-snippet. + * + * @param [expression\] The [RowExpression] to compute the value to sum for each row. + */ + @ExcludeFromSources + typealias ExpressionParam = Nothing + + /** + * @comment The `name` parameter of the [Grouped.sum] overloads. KDoc-snippet. + * + * @param [name\] The name of the resulting column. + * If `null` (the default), the name of the selected column is used if exactly one column + * is selected, and `"sum"` otherwise. + * This name needs to be unique, else a [DuplicateColumnPathInsertException] is thrown. + */ + @ExcludeFromSources + typealias ResultColumnNameParam = Nothing +} + +/** [The Sum Operation][SumDocs] */ +@ExcludeFromSources +private typealias SumDocsLink = Nothing + +/** {@set [SelectingColumns.OPERATION] [sum][sum]} */ +@ExcludeFromSources +private typealias SetSumOperationArg = Nothing + +/** {@set [SelectingColumns.OPERATION] [sumFor][sumFor]} */ +@ExcludeFromSources +private typealias SetSumForOperationArg = Nothing + +// endregion // region DataColumn +/** + * Returns the sum of the [Short] values in this [DataColumn], as an [Int]. + * + * @include [SumDocs.DataColumnSumSnippet] + * ```kotlin + * // The sum of all values in the "amount" column of `Short`s, as an `Int` + * df.amount.sum() + * ``` + * + * @return The sum of the values in this column, as an [Int]. + */ @JvmName("sumShort") -public fun DataColumn.sum(): Int = Aggregators.sum(false).aggregateSingleColumn(this) as Int - +public fun DataColumn.sum(): Int = Aggregators.sum(false, typeOf()).aggregateSingleColumn(this) as Int + +/** + * Returns the sum of the [Byte] values in this [DataColumn], as an [Int]. + * + * @include [SumDocs.DataColumnSumSnippet] + * ```kotlin + * // The sum of all values in the "amount" column of `Byte`s, as an `Int` + * df.amount.sum() + * ``` + * + * @return The sum of the values in this column, as an [Int]. + */ @JvmName("sumByte") -public fun DataColumn.sum(): Int = Aggregators.sum(false).aggregateSingleColumn(this) as Int - +public fun DataColumn.sum(): Int = Aggregators.sum(false, typeOf()).aggregateSingleColumn(this) as Int + +/** + * Returns the sum of the values in this [DataColumn]. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.DataColumnSumSnippet] + * ```kotlin + * // The sum of all ages in the "age" Int column + * df.age.sum() + * // The sum of all weights in the "weight" `Double?` column, ignoring `null` values + * df.weight.sum() + * ``` + * + * @include [SumDocs.SkipNanParam] + * @return The sum of the values in this column. + */ @Suppress("UNCHECKED_CAST") @JvmName("sumNumber") public fun DataColumn.sum(skipNaN: Boolean = skipNaNDefault): T & Any = - Aggregators.sum(skipNaN).aggregateSingleColumn(this) as (T & Any) - + Aggregators.sum(skipNaN, typeOf()).aggregateSingleColumn(this) as (T & Any) + +/** + * Returns the sum of the [Short] values that the given [expression] returns + * for each element of this [DataColumn], as an [Int]. + * + * @include [SumDocs.DataColumnSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataColumnSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all halved values in the "amount" column of `Short`s, as an `Int` + * df.amount.sumOf { (it / 2).toShort() } + * ``` + * @return The sum of the values [expression] returns, as an [Int]. + */ @JvmName("sumOfShort") @OverloadResolutionByLambdaReturnType public inline fun DataColumn.sumOf(crossinline expression: (C) -> V): Int = - Aggregators.sum(false).aggregateOf(this, expression) as Int - + Aggregators.sum(false, typeOf()).aggregateOf(this, expression) as Int + +/** + * Returns the sum of the [Byte] values that the given [expression] returns + * for each element of this [DataColumn], as an [Int]. + * + * @include [SumDocs.DataColumnSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataColumnSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all halved values in the "amount" column of `Byte`s, as an `Int` + * df.amount.sumOf { (it / 2).toByte() } + * ``` + * @return The sum of the values [expression] returns, as an [Int]. + */ @JvmName("sumOfByte") @OverloadResolutionByLambdaReturnType public inline fun DataColumn.sumOf(crossinline expression: (C) -> V): Int = - Aggregators.sum(false).aggregateOf(this, expression) as Int - + Aggregators.sum(false, typeOf()).aggregateOf(this, expression) as Int + +/** + * Returns the sum of the values that the given [expression] returns + * for each element of this [DataColumn]. + * + * @include [SumDocs.DataColumnSumOfSnippet] + * @set [SumDocs.DataColumnSumOfSnippet.EXAMPLE] + * ```kotlin + * // The total length of all first names in the "name"/"firstName" column + * df.name.firstName.sumOf { it.length } + * ``` + * @include [SumDocs.SkipNanParam] + * @return The sum of the values [expression] returns. + */ @Suppress("UNCHECKED_CAST") @JvmName("sumOfNumber") @OverloadResolutionByLambdaReturnType public inline fun DataColumn.sumOf( skipNaN: Boolean = skipNaNDefault, crossinline expression: (C) -> V, -): V & Any = Aggregators.sum(skipNaN).aggregateOf(this, expression) as (V & Any) +): V & Any = Aggregators.sum(skipNaN, typeOf()).aggregateOf(this, expression) as (V & Any) // endregion // region DataRow +/** + * Returns the sum of all the numbers in this [DataRow]. + * + * Only the values in the columns of a primitive number type (and in "mixed" [Number] columns) + * are taken into account; all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * Since the values of different columns are summed together, the result is the sum of all those + * values converted to their common type. + * + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroOnEmptyDefaultDoubleSnippet] + * + * See also: + * - [`rowSumOf()`][DataRow.rowSumOf] — the sum of the values of one specific number type in this row. + * - [`rowMean`][DataRow.rowMean] — the sum divided by the number of values. + * - [`sum`][DataFrame.sum] — the sum of the values in specific columns of a [DataFrame]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: {@include [DocumentationUrls.RowStatistics]} + * + * ### Example + * ```kotlin + * // The sum of all numbers ("age" and "weight") in the first row + * // Columns of other types ("name" and "address") are ignored + * df[0].rowSum() + * ``` + * + * @include [SumDocs.SkipNanParam] + * @return The sum of all the numbers in this row. + */ public fun DataRow<*>.rowSum(skipNaN: Boolean = skipNaNDefault): Number = - Aggregators.sum(skipNaN).aggregateOfRow(this, primitiveOrMixedNumberColumns()) - + Aggregators.sum(skipNaN, typeOf()).aggregateOfRow(this, primitiveOrMixedNumberColumns()) + +/** + * Returns the sum of the [Short] values in this [DataRow], as an [Int]. + * + * Only the values in the columns of type [Short] (or `Short?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Short` values in the first row, as an `Int` + * df[0].rowSumOf() + * ``` + * @return The sum of the [Short] values in this row, as an [Int]. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfShort") public inline fun DataRow<*>.rowSumOf(_kClass: KClass = Short::class): Int = rowSumOf(typeOf(), false) as Int +/** + * Returns the sum of the [Byte] values in this [DataRow], as an [Int]. + * + * Only the values in the columns of type [Byte] (or `Byte?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Byte` values in the first row, as an `Int` + * df[0].rowSumOf() + * ``` + * @return The sum of the [Byte] values in this row, as an [Int]. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfByte") public inline fun DataRow<*>.rowSumOf(_kClass: KClass = Byte::class): Int = rowSumOf(typeOf(), false) as Int +/** + * Returns the sum of the [Int] values in this [DataRow]. + * + * Only the values in the columns of type [Int] (or `Int?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Int` values ("age" and "weight") in the first row + * df[0].rowSumOf() + * ``` + * @return The sum of the [Int] values in this row. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfInt") public inline fun DataRow<*>.rowSumOf(_kClass: KClass = Int::class): Int = rowSumOf(typeOf(), false) as Int +/** + * Returns the sum of the [Long] values in this [DataRow]. + * + * Only the values in the columns of type [Long] (or `Long?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Long` values in the first row + * df[0].rowSumOf() + * ``` + * @return The sum of the [Long] values in this row. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfLong") public inline fun DataRow<*>.rowSumOf(_kClass: KClass = Long::class): Long = rowSumOf(typeOf(), false) as Long +/** + * Returns the sum of the [Float] values in this [DataRow]. + * + * Only the values in the columns of type [Float] (or `Float?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Float` values in the first row, ignoring `NaN` values + * df[0].rowSumOf(skipNaN = true) + * ``` + * @include [SumDocs.SkipNanParam] + * @return The sum of the [Float] values in this row. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfFloat") public inline fun DataRow<*>.rowSumOf( @@ -107,6 +908,23 @@ public inline fun DataRow<*>.rowSumOf( _kClass: KClass = Float::class, ): Float = rowSumOf(typeOf(), skipNaN) as Float +/** + * Returns the sum of the [Double] values in this [DataRow]. + * + * Only the values in the columns of type [Double] (or `Double?`) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * @include [SumDocs.ReifiedRowSumOfSnippet] + * @set [SumDocs.ReifiedRowSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all `Double` values in the first row, ignoring `NaN` values + * df[0].rowSumOf(skipNaN = true) + * ``` + * @include [SumDocs.SkipNanParam] + * @return The sum of the [Double] values in this row. + */ @Suppress("FINAL_UPPER_BOUND") @JvmName("rowSumOfDouble") public inline fun DataRow<*>.rowSumOf( @@ -115,29 +933,101 @@ public inline fun DataRow<*>.rowSumOf( ): Double = rowSumOf(typeOf(), skipNaN) as Double // unfortunately, we cannot make a `reified T : Number?` due to clashes + +/** + * Returns the sum of the values of the given [type] in this [DataRow]. + * + * Only the values in the columns of the given [type] (or its nullable variant) are taken into account; + * all other columns of the row are ignored. + * This includes columns inside [column groups][ColumnGroup]. + * To include those in the sum, [flatten][DataFrame.flatten] the DataFrame first. + * + * This overload takes the type as a [KType] argument; prefer the `reified` overloads, like + * [`rowSumOf`][DataRow.rowSumOf]`<`[`Int`][Int]`>()`, whenever the type is known at compile time. + * + * @include [SumDocs.RowSumOfSnippet] + * ```kotlin + * // The sum of all `Int` values ("age" and "weight") in the first row + * df[0].rowSumOf(typeOf()) + * ``` + * @param [type] The type of the values to sum. Only columns of this type are taken into account. + * @include [SumDocs.SkipNanParam] + * @return The sum of the values of the given [type] in this row. + * @throws IllegalArgumentException if [type] is not a primitive number type or [Number] itself. + */ public fun DataRow<*>.rowSumOf(type: KType, skipNaN: Boolean = skipNaNDefault): Number { require(type.isPrimitiveOrMixedNumber()) { - "Type $type is not a primitive number type. Mean only supports primitive number types." + "Type $type is not a primitive number type. Sum only supports primitive number types." } - return Aggregators.sum(skipNaN).aggregateOfRow(this) { + return Aggregators.sum(skipNaN, type).aggregateOfRow(this) { colsOf(type.withNullability(true)) } } // endregion // region DataFrame + +/** + * Returns the sum of the values of each suitable column of this [DataFrame] separately. + * + * @include [SumDocs.AllSuitableColumnsSnippet] + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * + * See also: + * - [`sumFor`][DataFrame.sumFor] — the same, but for an explicit selection of columns. + * - [`sum`][DataFrame.sum]` { columns }` — a single sum of all values in the selected columns. + * - [`mean`][DataFrame.mean] — the sum of each column divided by its number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * For more information: {@include [DocumentationUrls.Sum]} + * + * ### Example + * ```kotlin + * // A single row with the sum of each number column ("age" and "weight") + * df.sum() + * ``` + * + * @include [SumDocs.SkipNanParam] + * @return A single [DataRow] with the sum of each suitable column of this [DataFrame]. + */ @Refine @Interpretable("Sum0") public fun DataFrame.sum(skipNaN: Boolean = skipNaNDefault): DataRow = sumFor(skipNaN, primitiveOrMixedNumberColumns()) +/** + * @include [SumDocs.DataFrameSumForSnippet] + * @set [SumDocs.DataFrameSumForSnippet.NOTE] {@include [SumDocs.AggregateColumnsSelectorSnippet]} + * @set [SumDocs.DataFrameSumForSnippet.EXAMPLE] + * ```kotlin + * // A single row with the sum of the "age" values and the sum of the "weight" values + * df.sumFor { age and weight } + * // The same, ignoring `NaN` values, and naming the results explicitly + * df.sumFor(skipNaN = true) { age into "totalAge" and (weight into "totalWeight") } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.AggregateColumnsSelectorParam] + * @return A single [DataRow] with the sum of each selected column. + */ @Refine @Interpretable("Sum1") public fun DataFrame.sumFor( skipNaN: Boolean = skipNaNDefault, columns: ColumnsForAggregateSelector, -): DataRow = Aggregators.sum(skipNaN).aggregateFor(this, columns) - +): DataRow = Aggregators.sum(skipNaN, typeOf()).aggregateFor(this, columns) + +/** + * @include [SumDocs.DataFrameSumForSnippet] + * @set [SumDocs.DataFrameSumForSnippet.EXAMPLE] + * ```kotlin + * // A single row with the sum of the "age" values and the sum of the "weight" values + * df.sumFor("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SkipNanParam] + * @return A single [DataRow] with the sum of each selected column. + */ @Refine @StringApiInterpretable(interpreter = "Sum1", stringArgument = "columns", targetArgument = "columns") public fun DataFrame.sumFor(vararg columns: String, skipNaN: Boolean = skipNaNDefault): DataRow = @@ -157,23 +1047,73 @@ public fun DataFrame.sumFor( skipNaN: Boolean = skipNaNDefault, ): DataRow = sumFor(skipNaN) { columns.toColumnSet() } +/** + * Returns a single sum of all the [Short] values in the selected columns of this [DataFrame], + * as an [Int]. + * + * @include [SumDocs.DataFrameSumSnippet] + * {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataFrameSumSnippet.SEE_ALSO_TAIL] + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumSnippet.COLUMNS_API] {@include [SelectingColumns.ColumnsSelectionDsl]} + * @set [SumDocs.DataFrameSumSnippet.EXAMPLE] + * ```kotlin + * // The sum of all values in the "amount" and "bonus" columns of `Short`s, as an `Int` + * df.sum { amount and bonus } + * ``` + * @include [SumDocs.ColumnsSelectorParam] + * @return The sum of all the values in the selected columns, as an [Int]. + */ @JvmName("sumShort") @OverloadResolutionByLambdaReturnType public fun DataFrame.sum(columns: ColumnsSelector): Int = - Aggregators.sum(false).aggregateAll(this, columns) as Int - + Aggregators.sum(false, typeOf()).aggregateAll(this, columns) as Int + +/** + * Returns a single sum of all the [Byte] values in the selected columns of this [DataFrame], + * as an [Int]. + * + * @include [SumDocs.DataFrameSumSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataFrameSumSnippet.SEE_ALSO_TAIL] + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumSnippet.COLUMNS_API] {@include [SelectingColumns.ColumnsSelectionDsl]} + * @set [SumDocs.DataFrameSumSnippet.EXAMPLE] + * ```kotlin + * // The sum of all values in the "amount" and "bonus" columns of `Byte`s, as an `Int` + * df.sum { amount and bonus } + * ``` + * @include [SumDocs.ColumnsSelectorParam] + * @return The sum of all the values in the selected columns, as an [Int]. + */ @JvmName("sumByte") @OverloadResolutionByLambdaReturnType public fun DataFrame.sum(columns: ColumnsSelector): Int = - Aggregators.sum(false).aggregateAll(this, columns) as Int - + Aggregators.sum(false, typeOf()).aggregateAll(this, columns) as Int + +/** + * Returns a single sum of all the values in the selected columns of this [DataFrame]. + * + * @include [SumDocs.DataFrameSumSnippet] + * @set [SumDocs.DataFrameSumSnippet.SEE_ALSO_TAIL] + * - [`mean`][DataFrame.mean] — the sum divided by the number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumSnippet.COLUMNS_API] {@include [SelectingColumns.ColumnsSelectionDsl]} + * @set [SumDocs.DataFrameSumSnippet.EXAMPLE] + * ```kotlin + * // The sum of all values in the "age" and "weight" columns together + * df.sum { age and weight } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ColumnsSelectorParam] + * @return The sum of all the values in the selected columns. + */ @Suppress("UNCHECKED_CAST") @JvmName("sumNumber") @OverloadResolutionByLambdaReturnType public fun DataFrame.sum( skipNaN: Boolean = skipNaNDefault, columns: ColumnsSelector, -): C & Any = Aggregators.sum(skipNaN).aggregateAll(this, columns) as (C & Any) +): C & Any = Aggregators.sum(skipNaN, typeOf()).aggregateAll(this, columns) as (C & Any) @JvmName("sumShort") @Deprecated(DEPRECATED_ACCESS_API) @@ -193,6 +1133,23 @@ public fun DataFrame.sum( skipNaN: Boolean = skipNaNDefault, ): C & Any = sum(skipNaN) { columns.toColumnSet() } +/** + * Returns a single sum of all the values in the selected columns of this [DataFrame]. + * + * @include [SumDocs.DataFrameSumSnippet] + * @set [SumDocs.DataFrameSumSnippet.SEE_ALSO_TAIL] + * - [`mean`][DataFrame.mean] — the sum divided by the number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumSnippet.COLUMNS_API] {@include [SelectingColumns.ColumnNamesApi]} + * @set [SumDocs.DataFrameSumSnippet.EXAMPLE] + * ```kotlin + * // The sum of all values in the "age" and "weight" columns together + * df.sum("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SkipNanParam] + * @return The sum of all the values in the selected columns. + */ public fun DataFrame.sum(vararg columns: String, skipNaN: Boolean = skipNaNDefault): Number = sum(skipNaN) { columns.toColumnsSetOf() } @@ -214,39 +1171,139 @@ public fun DataFrame.sum( vararg columns: KProperty, ): C & Any = sum(skipNaN) { columns.toColumnSet() } +/** + * Returns the sum of the [Short] values that the given [expression] returns + * for each row of this [DataFrame], as an [Int]. + * + * @include [SumDocs.DataFrameSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataFrameSumOfSnippet.SEE_ALSO_TAIL] + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all halved "amount" values (of `Short`s), as an `Int` + * df.sumOf { (amount / 2).toShort() } + * ``` + * @include [SumDocs.ExpressionParam] + * @return The sum of the values [expression] returns, as an [Int]. + */ @JvmName("sumOfShort") @OverloadResolutionByLambdaReturnType public inline fun DataFrame.sumOf(crossinline expression: RowExpression): Int = - Aggregators.sum(false).aggregateOf(this, expression) as Int - + Aggregators.sum(false, typeOf()).aggregateOf(this, expression) as Int + +/** + * Returns the sum of the [Byte] values that the given [expression] returns + * for each row of this [DataFrame], as an [Int]. + * + * @include [SumDocs.DataFrameSumOfSnippet] {@set [SumDocs.SupportedTypesSnippet.NAN_NOTE]} + * @set [SumDocs.DataFrameSumOfSnippet.SEE_ALSO_TAIL] + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of all halved "amount" values (of `Byte`s), as an `Int` + * df.sumOf { (amount / 2).toByte() } + * ``` + * @include [SumDocs.ExpressionParam] + * @return The sum of the values [expression] returns, as an [Int]. + */ @JvmName("sumOfByte") @OverloadResolutionByLambdaReturnType public inline fun DataFrame.sumOf(crossinline expression: RowExpression): Int = - Aggregators.sum(false).aggregateOf(this, expression) as Int - + Aggregators.sum(false, typeOf()).aggregateOf(this, expression) as Int + +/** + * Returns the sum of the values that the given [expression] returns + * for each row of this [DataFrame]. + * + * @include [SumDocs.DataFrameSumOfSnippet] + * @set [SumDocs.DataFrameSumOfSnippet.SEE_ALSO_TAIL] + * - [`meanOf`][DataFrame.meanOf] — the sum of those values divided by the number of values. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * @set [SumDocs.DataFrameSumOfSnippet.EXAMPLE] + * ```kotlin + * // The sum of the weight-to-age ratios of all rows + * df.sumOf { (weight ?: 0) / age } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ExpressionParam] + * @return The sum of the values [expression] returns. + */ @Suppress("UNCHECKED_CAST") @JvmName("sumOfNumber") @OverloadResolutionByLambdaReturnType public inline fun DataFrame.sumOf( skipNaN: Boolean = skipNaNDefault, crossinline expression: RowExpression, -): C & Any = Aggregators.sum(skipNaN).aggregateOf(this, expression) as (C & Any) +): C & Any = Aggregators.sum(skipNaN, typeOf()).aggregateOf(this, expression) as (C & Any) // endregion // region GroupBy + +/** + * Aggregates this [GroupBy] by computing the sum of the values of + * each suitable column separately, per group. + * + * Returns a new [DataFrame] with one row per group, containing the group key columns + * and a column with the sum for each suitable column. + * + * @include [SumDocs.AllSuitableColumnsSnippet] + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * + * See also: + * - [`sumFor`][Grouped.sumFor] — the same, but for an explicit selection of columns. + * - [`sum`][Grouped.sum]` { columns }` — a single sum of all values in the selected columns, per group. + * - [`mean`][Grouped.mean] — the sum of each column divided by its number of values, per group. + * - [`aggregate`][Grouped.aggregate] — the general way to aggregate groups. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.GroupByUrlsSnippet] + * + * ### Example + * ```kotlin + * // For each city, the sum of each number column ("age" and "weight") + * df.groupBy { city }.sum() + * ``` + * + * @include [SumDocs.SkipNanParam] + * @return A new [DataFrame] with the group keys and the sum of each suitable column per group. + */ @Refine @Interpretable("GroupBySum1") public fun Grouped.sum(skipNaN: Boolean = skipNaNDefault): DataFrame = sumFor(skipNaN, primitiveOrMixedNumberColumns()) +/** + * @include [SumDocs.GroupedSumForSnippet] + * @set [SumDocs.GroupedSumForSnippet.NOTE] {@include [SumDocs.AggregateColumnsSelectorSnippet]} + * @set [SumDocs.GroupedSumForSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of the "age" values and the sum of the "weight" values + * df.groupBy { city }.sumFor { age and weight } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.AggregateColumnsSelectorParam] + * @return A new [DataFrame] with the group keys and the sum of each selected column per group. + */ @Refine @Interpretable("GroupBySum0") public fun Grouped.sumFor( skipNaN: Boolean = skipNaNDefault, columns: ColumnsForAggregateSelector, -): DataFrame = Aggregators.sum(skipNaN).aggregateFor(this, columns) - +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateFor(this, columns) + +/** + * @include [SumDocs.GroupedSumForSnippet] + * @set [SumDocs.GroupedSumForSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of the "age" values and the sum of the "weight" values + * df.groupBy { city }.sumFor("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SkipNanParam] + * @return A new [DataFrame] with the group keys and the sum of each selected column per group. + */ @Refine @StringApiInterpretable(interpreter = "GroupBySum0", stringArgument = "columns", targetArgument = "columns") public fun Grouped.sumFor(vararg columns: String, skipNaN: Boolean = skipNaNDefault): DataFrame = @@ -266,14 +1323,40 @@ public fun Grouped.sumFor( skipNaN: Boolean = skipNaNDefault, ): DataFrame = sumFor(skipNaN) { columns.toColumnSet() } +/** + * @include [SumDocs.GroupedSumSnippet] + * @set [SumDocs.GroupedSumSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of all values in the "age" and "weight" columns, + * // in a column called "total" + * df.groupBy { city }.sum("total") { age and weight } + * ``` + * @include [SumDocs.ResultColumnNameParam] + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ColumnsSelectorParam] + * @return A new [DataFrame] with the group keys and a single sum per group. + */ @Refine @Interpretable("GroupBySum2") public fun Grouped.sum( name: String? = null, skipNaN: Boolean = skipNaNDefault, columns: ColumnsSelector, -): DataFrame = Aggregators.sum(skipNaN).aggregateAll(this, name, columns) - +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateAll(this, name, columns) + +/** + * @include [SumDocs.GroupedSumSnippet] + * @set [SumDocs.GroupedSumSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of all values in the "age" and "weight" columns, + * // in a column called "total" + * df.groupBy { city }.sum("age", "weight", name = "total") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.ResultColumnNameParam] + * @include [SumDocs.SkipNanParam] + * @return A new [DataFrame] with the group keys and a single sum per group. + */ @Refine @StringApiInterpretable(interpreter = "GroupBySum2", stringArgument = "columns", targetArgument = "columns") public fun Grouped.sum( @@ -298,27 +1381,118 @@ public fun Grouped.sum( skipNaN: Boolean = skipNaNDefault, ): DataFrame = sum(name, skipNaN) { columns.toColumnSet() } +/** + * Aggregates this [GroupBy] by computing the sum of the values that the given [expression] + * returns for each row of a group. + * + * Returns a new [DataFrame] with one row per group, containing the group key columns and + * a single column with the sum per group, named [resultName] (or `"sum"` if [resultName] is `null`). + * + * @include [SumDocs.RowExpressionSnippet] + * + * The result of the [expression\] is considered the 'input' of this operation. + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * + * See also: + * - [`sum`][Grouped.sum] — a single sum of all values in the selected columns, per group. + * - [`meanOf`][Grouped.meanOf] — the sum of those values divided by the number of values, per group. + * - [`aggregate`][Grouped.aggregate] — the general way to aggregate groups. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.GroupByUrlsSnippet] + * + * ### Example + * ```kotlin + * // For each city, the sum of the weight-to-age ratios, in a column called "totalRatio" + * df.groupBy { city }.sumOf("totalRatio") { (weight ?: 0) / age } + * ``` + * + * @param [resultName] The name of the resulting column. If `null` (the default), `"sum"` is used. + * This name needs to be unique, else a [DuplicateColumnPathInsertException] is thrown. + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ExpressionParam] + * @return A new [DataFrame] with the group keys and a single sum per group. + */ @Refine @Interpretable("GroupBySumOf") public inline fun Grouped.sumOf( resultName: String? = null, skipNaN: Boolean = skipNaNDefault, crossinline expression: RowExpression, -): DataFrame = Aggregators.sum(skipNaN).aggregateOf(this, resultName, expression) +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateOf(this, resultName, expression) // endregion // region Pivot +/** + * Aggregates this [Pivot] by computing the sum of the values of + * each suitable column separately, per group. + * + * Returns a single [DataRow] with the [pivot] keys as (nested) columns, containing the sum + * of each suitable column of the corresponding group. + * + * @include [SumDocs.AllSuitableColumnsSnippet] + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * + * Check out the [`Pivot` Grammar][PivotDocs.Grammar]. + * + * See also: + * - [`sumFor`][Pivot.sumFor] — the same, but for an explicit selection of columns. + * - [`sum`][Pivot.sum]` { columns }` — a single sum of all values in the selected columns, per group. + * - [Pivot aggregation][PivotDocs.Aggregation] — all other ways to aggregate a [Pivot]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * ```kotlin + * // For each city, the sum of each number column ("age" and "weight") + * df.pivot { city }.sum() + * ``` + * + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @return A single [DataRow] with the sum of each suitable column per [pivot] group. + */ public fun Pivot.sum(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataRow = sumFor(separate, skipNaN, primitiveOrMixedNumberColumns()) +/** + * @include [SumDocs.PivotSumForSnippet] + * @set [SumDocs.PivotSumForSnippet.NOTE] {@include [SumDocs.AggregateColumnsSelectorSnippet]} + * @set [SumDocs.PivotSumForSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of the "age" values and the sum of the "weight" values + * df.pivot { city }.sumFor { age and weight } + * // The same, but with the results grouped by aggregated column instead of by city + * df.pivot { city }.sumFor(separate = true) { age and weight } + * ``` + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.AggregateColumnsSelectorParam] + * @return A single [DataRow] with the sum of each selected column per [pivot] group. + */ public fun Pivot.sumFor( separate: Boolean = false, skipNaN: Boolean = skipNaNDefault, columns: ColumnsForAggregateSelector, ): DataRow = delegate { sumFor(separate, skipNaN, columns) } +/** + * @include [SumDocs.PivotSumForSnippet] + * @set [SumDocs.PivotSumForSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of the "age" values and the sum of the "weight" values + * df.pivot { city }.sumFor("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @return A single [DataRow] with the sum of each selected column per [pivot] group. + */ public fun Pivot.sumFor( vararg columns: String, separate: Boolean = false, @@ -341,11 +1515,35 @@ public fun Pivot.sumFor( skipNaN: Boolean = skipNaNDefault, ): DataRow = sumFor(separate, skipNaN) { columns.toColumnSet() } +/** + * @include [SumDocs.PivotSumSnippet] + * @set [SumDocs.PivotSumSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of all values in the "age" and "weight" columns + * df.pivot { city }.sum { age and weight } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ColumnsSelectorParam] + * @return A single [DataRow] with, per [pivot] group, the sum of all the values + * in the selected columns. + */ public fun Pivot.sum( skipNaN: Boolean = skipNaNDefault, columns: ColumnsSelector, ): DataRow = delegate { sum(skipNaN, columns) } +/** + * @include [SumDocs.PivotSumSnippet] + * @set [SumDocs.PivotSumSnippet.EXAMPLE] + * ```kotlin + * // For each city, the sum of all values in the "age" and "weight" columns + * df.pivot { city }.sum("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SkipNanParam] + * @return A single [DataRow] with, per [pivot] group, the sum of all the values + * in the selected columns. + */ public fun Pivot.sum(vararg columns: String, skipNaN: Boolean = skipNaNDefault): DataRow = sum(skipNaN) { columns.toNumberColumns() } @@ -361,6 +1559,38 @@ public fun Pivot.sum( public fun Pivot.sum(vararg columns: KProperty, skipNaN: Boolean = skipNaNDefault): DataRow = sum(skipNaN) { columns.toColumnSet() } +/** + * Aggregates this [Pivot] by computing the sum of the values that the given [expression] + * returns for each row, per group. + * + * Returns a single [DataRow] with the [pivot] keys as (nested) columns, containing the sum + * of the expression's results for the rows of the corresponding group. + * + * @include [SumDocs.RowExpressionSnippet] + * + * The result of [expression\] is treated as the 'input' of this operation. + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptySnippet] + * + * Check out the [`Pivot` Grammar][PivotDocs.Grammar]. + * + * See also: + * - [`sum`][Pivot.sum]` { columns }` — a single sum of all values in the selected columns, per group. + * - [Pivot aggregation][PivotDocs.Aggregation] — all other ways to aggregate a [Pivot]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * ```kotlin + * // For each city, the sum of the weight-to-age ratios + * df.pivot { city }.sumOf { (weight ?: 0) / age } + * ``` + * + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ExpressionParam] + * @return A single [DataRow] with, per [pivot] group, the sum of the expression's results. + */ public inline fun Pivot.sumOf( skipNaN: Boolean = skipNaNDefault, crossinline expression: RowExpression, @@ -370,15 +1600,73 @@ public inline fun Pivot.sumOf( // region PivotGroupBy +/** + * Aggregates this [PivotGroupBy] by computing the sum of the values of + * each suitable column separately, per group. + * + * Returns a [DataFrame] where each cell contains the sum of each suitable column + * of the group corresponding to that [pivot] key (column) and [groupBy] key (row). + * + * @include [SumDocs.AllSuitableColumnsSnippet] + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * + * Check out the [`PivotGroupBy` Grammar][PivotGroupByDocs.Grammar]. + * + * See also: + * - [`sumFor`][PivotGroupBy.sumFor] — the same, but for an explicit selection of columns. + * - [`sum`][PivotGroupBy.sum]` { columns }` — a single sum of all values in the selected columns, + * per group. + * - [PivotGroupBy aggregation][PivotGroupByDocs.Aggregation] — all other ways to aggregate + * a [PivotGroupBy]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * ```kotlin + * // Per city and last name, the sum of each number column ("age" and "weight") + * df.pivot { city }.groupBy { name.lastName }.sum() + * ``` + * + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @return A [DataFrame] with the sum of each suitable column per group. + */ public fun PivotGroupBy.sum(separate: Boolean = false, skipNaN: Boolean = skipNaNDefault): DataFrame = sumFor(separate, skipNaN, primitiveOrMixedNumberColumns()) +/** + * @include [SumDocs.PivotGroupBySumForSnippet] + * @set [SumDocs.PivotGroupBySumForSnippet.NOTE] {@include [SumDocs.AggregateColumnsSelectorSnippet]} + * @set [SumDocs.PivotGroupBySumForSnippet.EXAMPLE] + * ```kotlin + * // Per city and last name, the sum of the "age" values and the sum of the "weight" values + * df.pivot { city }.groupBy { name.lastName }.sumFor { age and weight } + * ``` + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.AggregateColumnsSelectorParam] + * @return A [DataFrame] with the sum of each selected column per group. + */ public fun PivotGroupBy.sumFor( separate: Boolean = false, skipNaN: Boolean = skipNaNDefault, columns: ColumnsForAggregateSelector, -): DataFrame = Aggregators.sum(skipNaN).aggregateFor(this, separate, columns) - +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateFor(this, separate, columns) + +/** + * @include [SumDocs.PivotGroupBySumForSnippet] + * @set [SumDocs.PivotGroupBySumForSnippet.EXAMPLE] + * ```kotlin + * // Per city and last name, the sum of the "age" values and the sum of the "weight" values + * df.pivot { city }.groupBy { name.lastName }.sumFor("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SeparateParam] + * @include [SumDocs.SkipNanParam] + * @return A [DataFrame] with the sum of each selected column per group. + */ public fun PivotGroupBy.sumFor( vararg columns: String, separate: Boolean = false, @@ -401,11 +1689,33 @@ public fun PivotGroupBy.sumFor( skipNaN: Boolean = skipNaNDefault, ): DataFrame = sumFor(separate, skipNaN) { columns.toColumnSet() } +/** + * @include [SumDocs.PivotGroupBySumSnippet] + * @set [SumDocs.PivotGroupBySumSnippet.EXAMPLE] + * ```kotlin + * // Per city and last name, the sum of all values in the "age" and "weight" columns + * df.pivot { city }.groupBy { name.lastName }.sum { age and weight } + * ``` + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ColumnsSelectorParam] + * @return A [DataFrame] with, per group, the sum of all the values in the selected columns. + */ public fun PivotGroupBy.sum( skipNaN: Boolean = skipNaNDefault, columns: ColumnsSelector, -): DataFrame = Aggregators.sum(skipNaN).aggregateAll(this, columns) - +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateAll(this, columns) + +/** + * @include [SumDocs.PivotGroupBySumSnippet] + * @set [SumDocs.PivotGroupBySumSnippet.EXAMPLE] + * ```kotlin + * // Per city and last name, the sum of all values in the "age" and "weight" columns + * df.pivot { city }.groupBy { name.lastName }.sum("age", "weight") + * ``` + * @include [SumDocs.ColumnNamesParam] + * @include [SumDocs.SkipNanParam] + * @return A [DataFrame] with, per group, the sum of all the values in the selected columns. + */ public fun PivotGroupBy.sum(vararg columns: String, skipNaN: Boolean = skipNaNDefault): DataFrame = sum(skipNaN) { columns.toNumberColumns() } @@ -423,10 +1733,44 @@ public fun PivotGroupBy.sum( skipNaN: Boolean = skipNaNDefault, ): DataFrame = sum(skipNaN) { columns.toColumnSet() } +/** + * Aggregates this [PivotGroupBy] by computing the sum of the values that the given [expression] + * returns for each row, per group. + * + * Returns a [DataFrame] where each cell contains the sum of the expression's results for the + * rows of the group corresponding to that [pivot] key (column) and [groupBy] key (row). + * + * @include [SumDocs.RowExpressionSnippet] + * + * The result of the [expression\] is considered the 'input' of this operation. + * @include [SumDocs.SupportedTypesSnippet] + * @include [SumDocs.ZeroCellOnEmptyPivotSnippet] + * + * Check out the [`PivotGroupBy` Grammar][PivotGroupByDocs.Grammar]. + * + * See also: + * - [`sum`][PivotGroupBy.sum]` { columns }` — a single sum of all values in the selected columns, + * per group. + * - [PivotGroupBy aggregation][PivotGroupByDocs.Aggregation] — all other ways to aggregate + * a [PivotGroupBy]. + * - {@include [SumDocsLink]} — an overview of all `sum` modes. + * + * @include [SumDocs.PivotUrlsSnippet] + * + * ### Example + * ```kotlin + * // Per city and last name, the sum of the weight-to-age ratios + * df.pivot { city }.groupBy { name.lastName }.sumOf { (weight ?: 0) / age } + * ``` + * + * @include [SumDocs.SkipNanParam] + * @include [SumDocs.ExpressionParam] + * @return A [DataFrame] with, per group, the sum of the expression's results. + */ public inline fun PivotGroupBy.sumOf( skipNaN: Boolean = skipNaNDefault, crossinline expression: RowExpression, -): DataFrame = Aggregators.sum(skipNaN).aggregateOf(this, expression) +): DataFrame = Aggregators.sum(skipNaN, typeOf()).aggregateOf(this, expression) // endregion diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonMinMaxDocs.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonMinMaxDocs.kt index 179af5b776..5a7c7c630b 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonMinMaxDocs.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonMinMaxDocs.kt @@ -1,8 +1,6 @@ package org.jetbrains.kotlinx.dataframe.documentation import org.jetbrains.kotlinx.dataframe.DataFrame -import org.jetbrains.kotlinx.dataframe.RowExpression -import org.jetbrains.kotlinx.dataframe.aggregation.ColumnsForAggregateSelectionDsl import org.jetbrains.kotlinx.dataframe.api.GroupByDocs import org.jetbrains.kotlinx.dataframe.api.ReducedGroupBy import org.jetbrains.kotlinx.dataframe.api.ReducedPivot @@ -16,12 +14,14 @@ import org.jetbrains.kotlinx.dataframe.api.with * {@comment * Holds all KDoc-snippets that the `min` and `max` operations have in common. * Both `MinDocs` and `MaxDocs` inherit from this interface, so the snippets can be - * included from either of them, like `{@include [MaxDocs.SkipNaNParam]}`. + * included from either of them, like `{@include [MaxDocs.SkipNanParam]}`. + * The snippets that all summary statistics have in common are inherited from + * [CommonStatisticsDocs] and can be included in the same way. * * NOTE: this cannot be @ExcludedFromSources because `MinDocs` and `MaxDocs` use it as supertype. * } */ -internal interface CommonMinMaxDocs { +internal interface CommonMinMaxDocs : CommonStatisticsDocs { /** * {@comment Note about the self-comparability requirement and how `null` and `NaN` values @@ -31,10 +31,7 @@ internal interface CommonMinMaxDocs { * that are mutually comparable (like strings, primitive numbers, or dates). * This includes all primitive number types, but no mix of different number types. * - * `null` values in the input are always ignored. - * - * If the input contains {@include [NaNLink]} values, the result will be `NaN`, - * unless [skipNaN\] is set to `true`. + * {@include [CommonStatisticsDocs.NullAndNaNHandlingSnippet]} */ @ExcludeFromSources typealias InputValuesSnippet = Nothing @@ -72,29 +69,6 @@ internal interface CommonMinMaxDocs { @ExcludeFromSources typealias NullCellOnEmptySnippet = Nothing - /** - * {@comment Note about the row expression argument. KDoc-snippet.} - * - * The given [RowExpression] is evaluated for each row of the dataframe. - * The row is both the receiver and the argument (`it`) of the expression, - * so the values in it can be accessed directly. - * - * For more information: {@include [DocumentationUrls.DataRow.RowExpression]} - */ - @ExcludeFromSources - typealias RowExpressionSnippet = Nothing - - /** - * {@comment Note about the aggregate columns selector of the `-For` modes. KDoc-snippet.} - * - * The columns are selected with the [ColumnsForAggregateSelectionDsl] — an extension of the - * Columns Selection DSL which lets you rename the result of a column with - * [into][ColumnsForAggregateSelectionDsl.into] and supply a - * [default][ColumnsForAggregateSelectionDsl.default] value for columns without any values. - */ - @ExcludeFromSources - typealias AggregateColumnsSelectorSnippet = Nothing - /** * {@comment Note about [ReducedGroupBy] being an intermediate step. KDoc-snippet.} * @@ -127,24 +101,4 @@ internal interface CommonMinMaxDocs { */ @ExcludeFromSources typealias ReducedPivotGroupBySnippet = Nothing - - /** - * {@comment The shared `skipNaN` parameter documentation. KDoc-snippet.} - * - * @param [skipNaN\] If `true`, {@include [NaNLink]} values are ignored, just like `null` values. - * If `false` (the default), a {@include [NaNLink]} in the input is propagated to the result. - * Only has an effect on [Double] and [Float] values. - */ - @ExcludeFromSources - typealias SkipNaNParam = Nothing - - /** - * {@comment The shared `separate` parameter documentation. KDoc-snippet.} - * - * @param [separate\] If `false` (the default), the resulting columns are indexed - * first by the pivot key(s) and then by the names of the aggregated columns. - * If `true`, this order is reversed: the results are grouped by aggregated column first. - */ - @ExcludeFromSources - typealias SeparateParam = Nothing } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonStatisticsDocs.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonStatisticsDocs.kt new file mode 100644 index 0000000000..b63d63f63b --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/CommonStatisticsDocs.kt @@ -0,0 +1,88 @@ +package org.jetbrains.kotlinx.dataframe.documentation + +import org.jetbrains.kotlinx.dataframe.RowExpression +import org.jetbrains.kotlinx.dataframe.aggregation.ColumnsForAggregateSelectionDsl + +/** + * @comment + * Holds all KDoc-snippets that the summary statistics operations have in common. + * The KDoc-topic of each statistic (like [CommonMinMaxDocs] for `min`/`max`, or `SumDocs` for + * `sum`) inherits from this interface, so the snippets can be included from any of them, + * like `{@include [CommonMinMaxDocs.SkipNanParam]}`. + * + * NOTE: this cannot be @ExcludedFromSources because [CommonMinMaxDocs] and the other statistics + * KDoc-topics use it as supertype. + */ +internal interface CommonStatisticsDocs { + + /** + * {@comment Note about how `null` values in the input are treated. KDoc-snippet.} + * + * `null` values in the input are always ignored. + */ + @ExcludeFromSources + typealias NullHandlingSnippet = Nothing + + /** + * {@comment Note about how NaN values in the input are treated. KDoc-snippet. + * Only include this in the KDoc of overloads that actually have a `skipNaN` parameter; + * overloads of a fixed integer type ([Byte], [Short], [Int], [Long]) have none, + * as those types have no `NaN`.} + * + * If the input contains {@include [NaNLink]} values, the result will be `NaN`, + * unless [skipNaN\] is set to `true`. + */ + @ExcludeFromSources + typealias NaNHandlingSnippet = Nothing + + /** + * @comment [NullHandlingSnippet] and [NaNHandlingSnippet] combined. KDoc-snippet. + * @include [NullHandlingSnippet] + * @include [NaNHandlingSnippet] + */ + @ExcludeFromSources + typealias NullAndNaNHandlingSnippet = Nothing + + /** + * {@comment Note about the row expression argument. KDoc-snippet.} + * + * The given [RowExpression] is evaluated for each row of the dataframe. + * The row is both the receiver and the argument (`it`) of the expression, + * so the values in it can be accessed directly. + * + * For more information: {@include [DocumentationUrls.DataRow.RowExpression]} + */ + @ExcludeFromSources + typealias RowExpressionSnippet = Nothing + + /** + * {@comment Note about the aggregate columns selector of the `-For` modes. KDoc-snippet.} + * + * The columns are selected with the [ColumnsForAggregateSelectionDsl] — an extension of the + * Columns Selection DSL which lets you rename the result of a column with + * [`into`][ColumnsForAggregateSelectionDsl.into] and supply a + * [`default`][ColumnsForAggregateSelectionDsl.default] value for columns without any values. + */ + @ExcludeFromSources + typealias AggregateColumnsSelectorSnippet = Nothing + + /** + * @comment The shared `skipNaN` parameter documentation. KDoc-snippet. + * + * @param [skipNaN\] If `true`, {@include [NaNLink]} values are ignored, just like `null` values. + * If `false` (the default), a {@include [NaNLink]} in the input is propagated to the result. + * This only has an effect on [Double] and [Float] values. + */ + @ExcludeFromSources + typealias SkipNanParam = Nothing + + /** + * @comment The shared `separate` parameter documentation. KDoc-snippet. + * + * @param [separate\] If `false` (the default), the resulting columns are indexed + * first by the pivot key(s) and then by the names of the aggregated columns. + * If `true`, this order is reversed: the results are grouped by aggregated column first. + */ + @ExcludeFromSources + typealias SeparateParam = Nothing +} diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt index f4c6b52803..345b4035d2 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt @@ -326,6 +326,16 @@ public interface DocumentationUrls { /** [See "Row statistics" on the documentation website.]({@include [Url]}/rowstats.html) */ public typealias RowStatistics = Nothing + /** [See "Number Unification" on the documentation website.]({@include [Url]}/numberunification.html) */ + public typealias NumberUnification = Nothing + + /** [See `sum` on the documentation website.]({@include [Url]}/sum.html) */ + public interface Sum { + + /** [See "`sum` Type Conversion" on the documentation website.]({@include [Url]}/sum.html#type-conversion) */ + public typealias TypeConversion = Nothing + } + /** [See "min / max" on the documentation website.]({@include [Url]}/minmax.html) */ public interface MinMax { diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator.kt index 4746df815b..dcda080988 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator.kt @@ -123,7 +123,7 @@ internal fun Aggregator.aggregate( internal fun Aggregator.calculateValueType( values: Sequence, valueTypes: Set? = null, -) = if (valueTypes != null && valueTypes.isNotEmpty()) { +) = if (!valueTypes.isNullOrEmpty()) { calculateValueType(valueTypes) } else { calculateValueType(values) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorInputHandler.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorInputHandler.kt index 73c0dcde4b..7bc671d909 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorInputHandler.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/AggregatorInputHandler.kt @@ -1,5 +1,6 @@ package org.jetbrains.kotlinx.dataframe.impl.aggregation.aggregators +import org.jetbrains.kotlinx.dataframe.impl.nothingType import kotlin.reflect.KType /** @@ -10,9 +11,17 @@ import kotlin.reflect.KType */ public interface AggregatorInputHandler : AggregatorHandler { + /** + * The type of the value if the input is empty. + */ + public val valueTypeIfEmpty: KType + get() = nothingType + /** * If the specific [ValueType] of the input is not known, but you still want to call [aggregate], * this function can be called to calculate it by combining the set of known [valueTypes]. + * + * Returns [valueTypeIfEmpty] if [valueTypes] is empty. */ public fun calculateValueType(valueTypes: Set): ValueType @@ -22,6 +31,8 @@ public interface AggregatorInputHandler : Agg * If the specific [ValueType] of the input is not known, but you still want to call [aggregate], * this function can be called to calculate it by getting the types of [values] at runtime. * This is heavy because it uses reflection on each value. + * + * Returns [valueTypeIfEmpty] if [values] is empty. */ public fun calculateValueType(values: Sequence): ValueType diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt index 5cdede84f2..5dd388f806 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt @@ -26,6 +26,8 @@ import org.jetbrains.kotlinx.dataframe.math.std import org.jetbrains.kotlinx.dataframe.math.stdTypeConversion import org.jetbrains.kotlinx.dataframe.math.sum import org.jetbrains.kotlinx.dataframe.math.sumTypeConversion +import kotlin.reflect.KType +import kotlin.reflect.typeOf public object Aggregators { @@ -89,23 +91,25 @@ public object Aggregators { ) private fun flattenReducingForNumbers( + valueTypeIfEmpty: KType, getReturnType: CalculateReturnType, statisticsParameters: Map, reducer: Reducer, ) = Aggregator( aggregationHandler = ReducingAggregationHandler(reducer, getReturnType), - inputHandler = NumberInputHandler(), + inputHandler = NumberInputHandler(valueTypeIfEmpty), multipleColumnsHandler = FlatteningMultipleColumnsHandler(), statisticsParameters = statisticsParameters, ) private fun twoStepReducingForNumbers( + valueTypeIfEmpty: KType, getReturnType: CalculateReturnType, statisticsParameters: Map, reducer: Reducer, ) = Aggregator( aggregationHandler = ReducingAggregationHandler(reducer, getReturnType), - inputHandler = NumberInputHandler(), + inputHandler = NumberInputHandler(valueTypeIfEmpty), multipleColumnsHandler = TwoStepMultipleColumnsHandler(), statisticsParameters = statisticsParameters, ) @@ -154,6 +158,7 @@ public object Aggregators { ddof: Int, -> flattenReducingForNumbers( + valueTypeIfEmpty = typeOf(), getReturnType = stdTypeConversion, statisticsParameters = mapOf( "skipNaN" to skipNaN, @@ -169,6 +174,7 @@ public object Aggregators { // step two: Double -> Double public val mean: AggregatorOptionSwitch1 by withOneOption { skipNaN: Boolean -> twoStepReducingForNumbers( + valueTypeIfEmpty = typeOf(), getReturnType = meanTypeConversion, statisticsParameters = mapOf("skipNaN" to skipNaN), reducer = { type -> @@ -251,13 +257,15 @@ public object Aggregators { // Byte -> Int // Short -> Int // Nothing -> Double - public val sum: AggregatorOptionSwitch1 by withOneOption { skipNaN: Boolean -> - twoStepReducingForNumbers( - getReturnType = sumTypeConversion, - statisticsParameters = mapOf("skipNaN" to skipNaN), - reducer = { type -> - sum(type, skipNaN) - }, - ) - } + public val sum: AggregatorOptionSwitch2 + by withTwoOptions { skipNaN: Boolean, valueTypeIfEmpty: KType -> + twoStepReducingForNumbers( + valueTypeIfEmpty = valueTypeIfEmpty, + getReturnType = sumTypeConversion, + statisticsParameters = mapOf("skipNaN" to skipNaN), + reducer = { type -> + sum(type, skipNaN) + }, + ) + } } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/AnyInputHandler.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/AnyInputHandler.kt index b18592e1bf..a7de0a57c1 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/AnyInputHandler.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/AnyInputHandler.kt @@ -14,7 +14,8 @@ import kotlin.reflect.KType * * When calculating the value type, it will try to find the common type in terms of inheritance. */ -internal class AnyInputHandler : AggregatorInputHandler { +internal class AnyInputHandler(override val valueTypeIfEmpty: KType = nothingType) : + AggregatorInputHandler { /** No preprocessing is done on the input values. */ override fun preprocessAggregation( @@ -26,7 +27,14 @@ internal class AnyInputHandler : AggregatorIn * If the specific [ValueType] of the input is not known, but you still want to call [aggregate], * this function can be called to calculate it in terms of inheritance by combining the set of known [valueTypes]. */ - override fun calculateValueType(valueTypes: Set): ValueType = valueTypes.commonType(false).toValueType() + override fun calculateValueType(valueTypes: Set): ValueType = + if (valueTypes.isEmpty()) { + valueTypeIfEmpty.toValueType() + } else { + valueTypes + .commonType(false) + .toValueType() + } /** * WARNING: HEAVY! @@ -46,7 +54,7 @@ internal class AnyInputHandler : AggregatorIn } }.toSet() return if (classes.isEmpty()) { - nothingType(hasNulls) + valueTypeIfEmpty } else { classes.commonType(hasNulls) }.toValueType() diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/NumberInputHandler.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/NumberInputHandler.kt index 653f285d6d..b1f965823b 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/NumberInputHandler.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/inputHandlers/NumberInputHandler.kt @@ -28,7 +28,8 @@ import kotlin.reflect.typeOf * it will try to find the common type in terms of [number unification][UnifyingNumbers]. * Preprocessing will handle the conversion of the values in the input to this unified number type. */ -internal class NumberInputHandler : AggregatorInputHandler { +internal class NumberInputHandler(override val valueTypeIfEmpty: KType) : + AggregatorInputHandler { /** * Preprocesses the input values before aggregation. @@ -100,8 +101,11 @@ internal class NumberInputHandler : AggregatorInputHandler): ValueType { + if (valueTypes.isEmpty()) return valueTypeIfEmpty.toValueType() + val unifiedType = valueTypes.unifiedNumberTypeOrNull(UnifiedNumberTypeOptions.PRIMITIVES_ONLY) ?: typeOf().withNullability(valueTypes.any { it.isMarkedNullable }) if (!unifiedType.isPrimitiveOrMixedNumber() && !unifiedType.isNothing) { @@ -126,9 +130,16 @@ internal class NumberInputHandler : AggregatorInputHandler): ValueType = - calculateValueType(values.asIterable().types().toSet()) + override fun calculateValueType(values: Sequence): ValueType { + val list = values.toList() + return if (list.isEmpty()) { + valueTypeIfEmpty.toValueType() + } else { + calculateValueType(list.types().toSet()) + } + } override var aggregator: Aggregator? = null } diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/statistics/sum.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/statistics/sum.kt index f27395f910..a0d2f08231 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/statistics/sum.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/statistics/sum.kt @@ -8,14 +8,18 @@ import io.kotest.matchers.string.shouldContain import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.columnOf +import org.jetbrains.kotlinx.dataframe.api.convert import org.jetbrains.kotlinx.dataframe.api.dataFrameOf import org.jetbrains.kotlinx.dataframe.api.isEmpty import org.jetbrains.kotlinx.dataframe.api.rowSum import org.jetbrains.kotlinx.dataframe.api.rowSumOf +import org.jetbrains.kotlinx.dataframe.api.single import org.jetbrains.kotlinx.dataframe.api.sum import org.jetbrains.kotlinx.dataframe.api.sumFor import org.jetbrains.kotlinx.dataframe.api.sumOf +import org.jetbrains.kotlinx.dataframe.api.take import org.jetbrains.kotlinx.dataframe.api.toDataFrame +import org.jetbrains.kotlinx.dataframe.api.toFloat import org.jetbrains.kotlinx.dataframe.impl.nullableNothingType import org.junit.Test import kotlin.reflect.typeOf @@ -136,6 +140,16 @@ class SumTests { shouldThrow { columnOf(1.0, 2, 3.0.toBigDecimal()).toDataFrame().sum()[0] }.message?.lowercase() shouldContain "primitive" + + // empty column of Number should be treated as Double + columnOf(1.0, 2) + .take(0) + .sum() shouldBe 0.0 + + // empty column in DF + columnOf(1.0, 2).toDataFrame() + .take(0) + .sum()[0] shouldBe 0.0 } @Test @@ -194,6 +208,17 @@ class SumTests { row2.rowSumOf().shouldBeNaN() } + @Test + fun `rowSumOf with empty input`() { + val df = dataFrameOf("a", "b")(1.0, 2.0) + df.single().rowSumOf() shouldBe 0 + df.single().rowSumOf() shouldBe 0 + df.single().rowSumOf() shouldBe 0 + df.single().rowSumOf() shouldBe 0f + df.convert { all() }.toFloat() + .single().rowSumOf() shouldBe 0.0 + } + @Test fun `test sumFor with skipNaN`() { val value1 by columnOf(1.0, 2.0, 3.0)