Skip to content

Commit 678075b

Browse files
committed
Merge branch 'refs/heads/master' into column-selector-docs3
2 parents eb8d1e4 + aa24b45 commit 678075b

File tree

23 files changed

+232
-69
lines changed

23 files changed

+232
-69
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Kotlin Dataframe: typesafe in-memory structured data processing for JVM
1+
# Kotlin DataFrame: typesafe in-memory structured data processing for JVM
22
[![JetBrains incubator project](https://jb.gg/badges/incubator.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
33
[![Kotlin component alpha stability](https://img.shields.io/badge/project-alpha-kotlin.svg?colorA=555555&colorB=DB3683&label=&logo=kotlin&logoColor=ffffff&logoWidth=10)](https://kotlinlang.org/docs/components-stability.html)
44
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.22-blue.svg?logo=kotlin)](http://kotlinlang.org)
@@ -8,7 +8,7 @@
88
[![GitHub License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)
99
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Kotlin/dataframe/HEAD)
1010

11-
Kotlin Dataframe aims to reconcile Kotlin's static typing with the dynamic nature of data by utilizing both the full power of the Kotlin language and the opportunities provided by intermittent code execution in Jupyter notebooks and REPL.
11+
Kotlin DataFrame aims to reconcile Kotlin's static typing with the dynamic nature of data by utilizing both the full power of the Kotlin language and the opportunities provided by intermittent code execution in Jupyter notebooks and REPL.
1212

1313
* **Hierarchical** — represents hierarchical data structures, such as JSON or a tree of JVM objects.
1414
* **Functional** — data processing pipeline is organized in a chain of `DataFrame` transformation operations. Every operation returns a new instance of `DataFrame` reusing underlying storage wherever it's possible.
@@ -18,7 +18,7 @@ Kotlin Dataframe aims to reconcile Kotlin's static typing with the dynamic natur
1818
* **Interoperable** — convertable with Kotlin data classes and collections.
1919
* **Generic** — can store objects of any type, not only numbers or strings.
2020
* **Typesafe** — on-the-fly generation of extension properties for type safe data access with Kotlin-style care for null safety.
21-
* **Polymorphic** — type compatibility derives from column schema compatibility. You can define a function that requires a special subset of columns in dataframe but doesn't care about other columns.
21+
* **Polymorphic** — type compatibility derives from column schema compatibility. You can define a function that requires a special subset of columns in a dataframe but doesn't care about other columns.
2222

2323
Integrates with [Kotlin kernel for Jupyter](https://github.com/Kotlin/kotlin-jupyter). Inspired by [krangl](https://github.com/holgerbrandl/krangl), Kotlin Collections and [pandas](https://pandas.pydata.org/)
2424

@@ -33,7 +33,7 @@ You could find the following articles there:
3333
* [Full list of all supported operations](https://kotlin.github.io/dataframe/operations.html)
3434
* [Reading from SQL databases](https://kotlin.github.io/dataframe/readsqldatabases.html)
3535
* [Reading/writing from/to different file formats like JSON, CSV, Apache Arrow](https://kotlin.github.io/dataframe/read.html)
36-
* [Joining a few dataframes](https://kotlin.github.io/dataframe/join.html)
36+
* [Joining dataframes](https://kotlin.github.io/dataframe/join.html)
3737
* [GroupBy operation](https://kotlin.github.io/dataframe/groupby.html)
3838
* [Rendering to HTML](https://kotlin.github.io/dataframe/tohtml.html#jupyter-notebooks)
3939

@@ -99,13 +99,13 @@ fun main() {
9999

100100
## Getting started in Jupyter Notebook / Kotlin Notebook
101101

102-
Install [Kotlin kernel](https://github.com/Kotlin/kotlin-jupyter) for [Jupyter](https://jupyter.org/)
102+
Install the [Kotlin kernel](https://github.com/Kotlin/kotlin-jupyter) for [Jupyter](https://jupyter.org/)
103103

104-
Import stable `dataframe` version into notebook:
104+
Import the stable `dataframe` version into a notebook:
105105
```
106106
%use dataframe
107107
```
108-
or specific version:
108+
or a specific version:
109109
```
110110
%use dataframe(<version>)
111111
```
@@ -205,7 +205,7 @@ clean
205205
}
206206
```
207207

208-
Check it out on [**Datalore**](https://datalore.jetbrains.com/view/notebook/vq5j45KWkYiSQnACA2Ymij) to get a better visual impression of what happens and what the hierarchical DataFrame structure looks like.
208+
Check it out on [**Datalore**](https://datalore.jetbrains.com/view/notebook/vq5j45KWkYiSQnACA2Ymij) to get a better visual impression of what happens and what the hierarchical dataframe structure looks like.
209209

210210
Explore [**more examples here**](examples).
211211

@@ -229,4 +229,4 @@ This project and the corresponding community are governed by the [JetBrains Open
229229

230230
## License
231231

232-
Kotlin Dataframe is licensed under the [Apache 2.0 License](LICENSE).
232+
Kotlin DataFrame is licensed under the [Apache 2.0 License](LICENSE).

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package org.jetbrains.kotlinx.dataframe.api
22

33
import org.jetbrains.kotlinx.dataframe.AnyCol
44
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
5+
import org.jetbrains.kotlinx.dataframe.impl.isNothing
6+
import org.jetbrains.kotlinx.dataframe.impl.projectTo
57
import org.jetbrains.kotlinx.dataframe.type
68
import org.jetbrains.kotlinx.dataframe.typeClass
79
import kotlin.reflect.KClass
810
import kotlin.reflect.KType
11+
import kotlin.reflect.KTypeProjection
912
import kotlin.reflect.full.isSubclassOf
1013
import kotlin.reflect.full.isSubtypeOf
1114
import kotlin.reflect.typeOf
@@ -16,7 +19,8 @@ public fun AnyCol.isFrameColumn(): Boolean = kind() == ColumnKind.Frame
1619

1720
public fun AnyCol.isValueColumn(): Boolean = kind() == ColumnKind.Value
1821

19-
public fun AnyCol.isSubtypeOf(type: KType): Boolean = this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable)
22+
public fun AnyCol.isSubtypeOf(type: KType): Boolean =
23+
this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable)
2024

2125
public inline fun <reified T> AnyCol.isSubtypeOf(): Boolean = isSubtypeOf(typeOf<T>())
2226

@@ -26,9 +30,23 @@ public fun AnyCol.isNumber(): Boolean = isSubtypeOf<Number?>()
2630

2731
public fun AnyCol.isList(): Boolean = typeClass == List::class
2832

29-
public fun AnyCol.isComparable(): Boolean = isSubtypeOf<Comparable<*>?>()
33+
/**
34+
* Returns `true` if [this] column is comparable, i.e. its type is a subtype of [Comparable] and its
35+
* type argument is not [Nothing].
36+
*/
37+
public fun AnyCol.isComparable(): Boolean =
38+
isSubtypeOf<Comparable<*>?>() &&
39+
type().projectTo(Comparable::class).arguments[0].let {
40+
it != KTypeProjection.STAR &&
41+
it.type?.isNothing != true
42+
}
3043

3144
@PublishedApi
3245
internal fun AnyCol.isPrimitive(): Boolean = typeClass.isPrimitive()
3346

34-
internal fun KClass<*>.isPrimitive(): Boolean = isSubclassOf(Number::class) || this == String::class || this == Char::class || this == Array::class || isSubclassOf(Collection::class)
47+
internal fun KClass<*>.isPrimitive(): Boolean =
48+
isSubclassOf(Number::class) ||
49+
this == String::class ||
50+
this == Char::class ||
51+
this == Array::class ||
52+
isSubclassOf(Collection::class)

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/TypeUtils.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import kotlin.reflect.KType
1212
import kotlin.reflect.KTypeParameter
1313
import kotlin.reflect.KTypeProjection
1414
import kotlin.reflect.KVariance
15-
import kotlin.reflect.KVariance.*
15+
import kotlin.reflect.KVariance.IN
16+
import kotlin.reflect.KVariance.INVARIANT
17+
import kotlin.reflect.KVariance.OUT
1618
import kotlin.reflect.KVisibility
1719
import kotlin.reflect.full.allSuperclasses
1820
import kotlin.reflect.full.createType
@@ -463,6 +465,9 @@ internal fun guessValueType(values: Sequence<Any?>, upperBound: KType? = null, l
463465
}
464466
}
465467

468+
internal val KType.isNothing: Boolean
469+
get() = classifier == Nothing::class
470+
466471
internal fun nothingType(nullable: Boolean): KType =
467472
if (nullable) {
468473
typeOf<List<Nothing?>>()

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ internal object Aggregators {
1616
private fun <C : Any, R> mergedValues(aggregate: Iterable<C?>.(KType) -> R?) =
1717
MergedValuesAggregator.Factory(aggregate, true)
1818

19+
private fun <C : Any, R> mergedValuesChangingTypes(aggregate: Iterable<C?>.(KType) -> R?) =
20+
MergedValuesAggregator.Factory(aggregate, false)
21+
1922
private fun <C, R> changesType(aggregate1: Iterable<C>.(KType) -> R, aggregate2: Iterable<R>.(KType) -> R) =
2023
TwoStepAggregator.Factory(aggregate1, aggregate2, false)
2124

@@ -33,7 +36,7 @@ internal object Aggregators {
3336
val max by preservesType<Comparable<Any?>> { maxOrNull() }
3437

3538
val std by withOption2<Boolean, Int, Number, Double> { skipNA, ddof ->
36-
mergedValues { std(it, skipNA, ddof) }
39+
mergedValuesChangingTypes { std(it, skipNA, ddof) }
3740
}
3841

3942
val mean by withOption<Boolean, Number, Double> { skipNA ->

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/guess.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ public sealed interface SupportedFormat {
3535
public sealed interface SupportedFormatSample {
3636

3737
@JvmInline
38-
public value class File(public val sampleFile: java.io.File) : SupportedFormatSample
38+
public value class DataFile(public val sampleFile: File) : SupportedFormatSample
3939

4040
@JvmInline
41-
public value class URL(public val sampleUrl: java.net.URL) : SupportedFormatSample
41+
public value class DataUrl(public val sampleUrl: URL) : SupportedFormatSample
4242

4343
@JvmInline
4444
public value class PathString(public val samplePath: String) : SupportedFormatSample
@@ -138,13 +138,13 @@ internal fun guessFormatForExtension(
138138
internal fun guessFormat(
139139
file: File,
140140
formats: List<SupportedFormat> = supportedFormats,
141-
sample: SupportedFormatSample.File? = SupportedFormatSample.File(file),
141+
sample: SupportedFormatSample.DataFile? = SupportedFormatSample.DataFile(file),
142142
): SupportedFormat? = guessFormatForExtension(file.extension.lowercase(), formats, sample = sample)
143143

144144
internal fun guessFormat(
145145
url: URL,
146146
formats: List<SupportedFormat> = supportedFormats,
147-
sample: SupportedFormatSample.URL? = SupportedFormatSample.URL(url),
147+
sample: SupportedFormatSample.DataUrl? = SupportedFormatSample.DataUrl(url),
148148
): SupportedFormat? = guessFormatForExtension(url.path.substringAfterLast("."), formats, sample = sample)
149149

150150
internal fun guessFormat(

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/statistics/std.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.dataframe.statistics
22

33
import io.kotest.matchers.shouldBe
44
import org.jetbrains.kotlinx.dataframe.api.columnOf
5+
import org.jetbrains.kotlinx.dataframe.api.columnTypes
56
import org.jetbrains.kotlinx.dataframe.api.dataFrameOf
67
import org.jetbrains.kotlinx.dataframe.api.std
78
import org.jetbrains.kotlinx.dataframe.math.std
@@ -21,6 +22,7 @@ class StdTests {
2122
value.std() shouldBe expected
2223
df[value].std() shouldBe expected
2324
df.std { value } shouldBe expected
25+
df.std().columnTypes().single() shouldBe typeOf<Double>()
2426
}
2527

2628
@Test

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/testSets/person/DataFrameTests.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import io.kotest.matchers.shouldBe
77
import io.kotest.matchers.shouldNotBe
88
import org.jetbrains.kotlinx.dataframe.AnyFrame
99
import org.jetbrains.kotlinx.dataframe.AnyRow
10+
import org.jetbrains.kotlinx.dataframe.DataColumn
1011
import org.jetbrains.kotlinx.dataframe.DataFrame
1112
import org.jetbrains.kotlinx.dataframe.DataRow
1213
import org.jetbrains.kotlinx.dataframe.RowExpression
@@ -79,6 +80,7 @@ import org.jetbrains.kotlinx.dataframe.api.intoColumns
7980
import org.jetbrains.kotlinx.dataframe.api.intoList
8081
import org.jetbrains.kotlinx.dataframe.api.intoRows
8182
import org.jetbrains.kotlinx.dataframe.api.isColumnGroup
83+
import org.jetbrains.kotlinx.dataframe.api.isComparable
8284
import org.jetbrains.kotlinx.dataframe.api.isEmpty
8385
import org.jetbrains.kotlinx.dataframe.api.isFrameColumn
8486
import org.jetbrains.kotlinx.dataframe.api.isNA
@@ -119,6 +121,7 @@ import org.jetbrains.kotlinx.dataframe.api.rename
119121
import org.jetbrains.kotlinx.dataframe.api.reorderColumnsByName
120122
import org.jetbrains.kotlinx.dataframe.api.replace
121123
import org.jetbrains.kotlinx.dataframe.api.rows
124+
import org.jetbrains.kotlinx.dataframe.api.schema
122125
import org.jetbrains.kotlinx.dataframe.api.select
123126
import org.jetbrains.kotlinx.dataframe.api.single
124127
import org.jetbrains.kotlinx.dataframe.api.sortBy
@@ -165,18 +168,22 @@ import org.jetbrains.kotlinx.dataframe.exceptions.ExcessiveColumnsException
165168
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConversionException
166169
import org.jetbrains.kotlinx.dataframe.get
167170
import org.jetbrains.kotlinx.dataframe.hasNulls
171+
import org.jetbrains.kotlinx.dataframe.impl.DataFrameImpl
168172
import org.jetbrains.kotlinx.dataframe.impl.DataFrameSize
169173
import org.jetbrains.kotlinx.dataframe.impl.api.convertToImpl
170174
import org.jetbrains.kotlinx.dataframe.impl.between
171175
import org.jetbrains.kotlinx.dataframe.impl.columns.isMissingColumn
172176
import org.jetbrains.kotlinx.dataframe.impl.emptyPath
173177
import org.jetbrains.kotlinx.dataframe.impl.getColumnsImpl
178+
import org.jetbrains.kotlinx.dataframe.impl.isNothing
174179
import org.jetbrains.kotlinx.dataframe.impl.nothingType
180+
import org.jetbrains.kotlinx.dataframe.impl.projectTo
175181
import org.jetbrains.kotlinx.dataframe.impl.trackColumnAccess
176182
import org.jetbrains.kotlinx.dataframe.index
177183
import org.jetbrains.kotlinx.dataframe.io.renderValueForStdout
178184
import org.jetbrains.kotlinx.dataframe.kind
179185
import org.jetbrains.kotlinx.dataframe.math.mean
186+
import org.jetbrains.kotlinx.dataframe.name
180187
import org.jetbrains.kotlinx.dataframe.ncol
181188
import org.jetbrains.kotlinx.dataframe.nrow
182189
import org.jetbrains.kotlinx.dataframe.size
@@ -2358,6 +2365,47 @@ class DataFrameTests : BaseTest() {
23582365
desc.print()
23592366
}
23602367

2368+
@DataSchema
2369+
data class ComparableTest(
2370+
val int: Int,
2371+
val comparableInt: Comparable<Int>,
2372+
val string: String,
2373+
val comparableString: Comparable<String>,
2374+
val comparableStar: Comparable<*>,
2375+
val comparableNothing: Comparable<Nothing>,
2376+
)
2377+
2378+
@Test
2379+
fun `is comparable`() {
2380+
val df = listOf(
2381+
ComparableTest(1, 1, "a", "a", 1, 1),
2382+
ComparableTest(2, 2, "b", "b", "2", "2"),
2383+
).toDataFrame()
2384+
2385+
df.int.isComparable() shouldBe true
2386+
df.comparableInt.isComparable() shouldBe true
2387+
df.string.isComparable() shouldBe true
2388+
df.comparableString.isComparable() shouldBe true
2389+
df.comparableStar.isComparable() shouldBe false
2390+
df.comparableNothing.isComparable() shouldBe false
2391+
}
2392+
2393+
@Test
2394+
fun `describe twice minimal`() {
2395+
val df = dataFrameOf("a", "b")(1, "foo", 3, "bar")
2396+
val desc1 = df.describe()
2397+
val desc2 = desc1.describe()
2398+
desc2::class shouldBe DataFrameImpl::class
2399+
}
2400+
2401+
@Test
2402+
fun `describe twice`() {
2403+
val df = typed.group { age and weight }.into("info").groupBy { city }.toDataFrame()
2404+
val desc1 = df.describe()
2405+
val desc2 = desc1.describe()
2406+
desc2::class shouldBe DataFrameImpl::class
2407+
}
2408+
23612409
@Test
23622410
fun `index by column accessor`() {
23632411
val col = listOf(1, 2, 3, 4, 5).toColumn("name")

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package org.jetbrains.kotlinx.dataframe.api
22

33
import org.jetbrains.kotlinx.dataframe.AnyCol
44
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
5+
import org.jetbrains.kotlinx.dataframe.impl.isNothing
6+
import org.jetbrains.kotlinx.dataframe.impl.projectTo
57
import org.jetbrains.kotlinx.dataframe.type
68
import org.jetbrains.kotlinx.dataframe.typeClass
79
import kotlin.reflect.KClass
810
import kotlin.reflect.KType
11+
import kotlin.reflect.KTypeProjection
912
import kotlin.reflect.full.isSubclassOf
1013
import kotlin.reflect.full.isSubtypeOf
1114
import kotlin.reflect.typeOf
@@ -16,7 +19,8 @@ public fun AnyCol.isFrameColumn(): Boolean = kind() == ColumnKind.Frame
1619

1720
public fun AnyCol.isValueColumn(): Boolean = kind() == ColumnKind.Value
1821

19-
public fun AnyCol.isSubtypeOf(type: KType): Boolean = this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable)
22+
public fun AnyCol.isSubtypeOf(type: KType): Boolean =
23+
this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable)
2024

2125
public inline fun <reified T> AnyCol.isSubtypeOf(): Boolean = isSubtypeOf(typeOf<T>())
2226

@@ -26,9 +30,23 @@ public fun AnyCol.isNumber(): Boolean = isSubtypeOf<Number?>()
2630

2731
public fun AnyCol.isList(): Boolean = typeClass == List::class
2832

29-
public fun AnyCol.isComparable(): Boolean = isSubtypeOf<Comparable<*>?>()
33+
/**
34+
* Returns `true` if [this] column is comparable, i.e. its type is a subtype of [Comparable] and its
35+
* type argument is not [Nothing].
36+
*/
37+
public fun AnyCol.isComparable(): Boolean =
38+
isSubtypeOf<Comparable<*>?>() &&
39+
type().projectTo(Comparable::class).arguments[0].let {
40+
it != KTypeProjection.STAR &&
41+
it.type?.isNothing != true
42+
}
3043

3144
@PublishedApi
3245
internal fun AnyCol.isPrimitive(): Boolean = typeClass.isPrimitive()
3346

34-
internal fun KClass<*>.isPrimitive(): Boolean = isSubclassOf(Number::class) || this == String::class || this == Char::class || this == Array::class || isSubclassOf(Collection::class)
47+
internal fun KClass<*>.isPrimitive(): Boolean =
48+
isSubclassOf(Number::class) ||
49+
this == String::class ||
50+
this == Char::class ||
51+
this == Array::class ||
52+
isSubclassOf(Collection::class)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/TypeUtils.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import kotlin.reflect.KType
1212
import kotlin.reflect.KTypeParameter
1313
import kotlin.reflect.KTypeProjection
1414
import kotlin.reflect.KVariance
15-
import kotlin.reflect.KVariance.*
15+
import kotlin.reflect.KVariance.IN
16+
import kotlin.reflect.KVariance.INVARIANT
17+
import kotlin.reflect.KVariance.OUT
1618
import kotlin.reflect.KVisibility
1719
import kotlin.reflect.full.allSuperclasses
1820
import kotlin.reflect.full.createType
@@ -463,6 +465,9 @@ internal fun guessValueType(values: Sequence<Any?>, upperBound: KType? = null, l
463465
}
464466
}
465467

468+
internal val KType.isNothing: Boolean
469+
get() = classifier == Nothing::class
470+
466471
internal fun nothingType(nullable: Boolean): KType =
467472
if (nullable) {
468473
typeOf<List<Nothing?>>()

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregators.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ internal object Aggregators {
1616
private fun <C : Any, R> mergedValues(aggregate: Iterable<C?>.(KType) -> R?) =
1717
MergedValuesAggregator.Factory(aggregate, true)
1818

19+
private fun <C : Any, R> mergedValuesChangingTypes(aggregate: Iterable<C?>.(KType) -> R?) =
20+
MergedValuesAggregator.Factory(aggregate, false)
21+
1922
private fun <C, R> changesType(aggregate1: Iterable<C>.(KType) -> R, aggregate2: Iterable<R>.(KType) -> R) =
2023
TwoStepAggregator.Factory(aggregate1, aggregate2, false)
2124

@@ -33,7 +36,7 @@ internal object Aggregators {
3336
val max by preservesType<Comparable<Any?>> { maxOrNull() }
3437

3538
val std by withOption2<Boolean, Int, Number, Double> { skipNA, ddof ->
36-
mergedValues { std(it, skipNA, ddof) }
39+
mergedValuesChangingTypes { std(it, skipNA, ddof) }
3740
}
3841

3942
val mean by withOption<Boolean, Number, Double> { skipNA ->

0 commit comments

Comments
 (0)