Skip to content

This fixes Nothing #795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,19 @@ internal fun commonParent(vararg classes: KClass<*>): KClass<*>? = commonParent(
internal fun Iterable<KClass<*>>.withMostSuperclasses(): KClass<*>? = maxByOrNull { it.allSuperclasses.size }

internal fun Iterable<KClass<*>>.createType(nullable: Boolean, upperBound: KType? = null): KType =
if (upperBound == null) {
(withMostSuperclasses() ?: Any::class).createStarProjectedType(nullable)
} else {
val upperClass = upperBound.classifier as KClass<*>
val baseClass = filter { it.isSubclassOf(upperClass) }.withMostSuperclasses() ?: withMostSuperclasses()
if (baseClass == null) {
upperBound.withNullability(nullable)
} else {
upperBound.projectTo(baseClass).withNullability(nullable)
when {
!iterator().hasNext() -> upperBound?.withNullability(nullable) ?: nothingType(nullable)

upperBound == null -> (withMostSuperclasses() ?: Any::class).createStarProjectedType(nullable)

else -> {
val upperClass = upperBound.classifier as KClass<*>
val baseClass = filter { it.isSubclassOf(upperClass) }.withMostSuperclasses() ?: withMostSuperclasses()
if (baseClass == null) {
upperBound.withNullability(nullable)
} else {
upperBound.projectTo(baseClass).withNullability(nullable)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import org.jetbrains.kotlinx.dataframe.columns.size
import org.jetbrains.kotlinx.dataframe.columns.values
import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
import org.jetbrains.kotlinx.dataframe.impl.columns.asAnyFrameColumn
import org.jetbrains.kotlinx.dataframe.impl.renderType
import org.jetbrains.kotlinx.dataframe.index
import org.jetbrains.kotlinx.dataframe.kind
import org.jetbrains.kotlinx.dataframe.type
import kotlin.reflect.jvm.jvmErasure

internal fun describeImpl(cols: List<AnyCol>): DataFrame<ColumnDescription> {
fun List<AnyCol>.collectAll(atAnyDepth: Boolean): List<AnyCol> =
Expand Down Expand Up @@ -65,7 +65,7 @@ internal fun describeImpl(cols: List<AnyCol>): DataFrame<ColumnDescription> {
if (hasLongPaths) {
ColumnDescription::path from { it.path() }
}
ColumnDescription::type from { buildTypeName(it) }
ColumnDescription::type from { renderType(it.type) }
ColumnDescription::count from { it.size }
ColumnDescription::unique from { it.countDistinct() }
ColumnDescription::nulls from { it.values.count { it == null } }
Expand Down Expand Up @@ -94,12 +94,3 @@ internal fun describeImpl(cols: List<AnyCol>): DataFrame<ColumnDescription> {

return df.cast()
}

private fun buildTypeName(it: AnyCol): String {
val rawJavaType = it.type.jvmErasure.simpleName.toString()
return if (it.type.isMarkedNullable) {
"$rawJavaType?"
} else {
rawJavaType
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.kotlinx.dataframe.math

import org.jetbrains.kotlinx.dataframe.api.skipNA_default
import org.jetbrains.kotlinx.dataframe.impl.renderType
import java.math.BigDecimal
import kotlin.reflect.KType
import kotlin.reflect.full.withNullability
Expand Down Expand Up @@ -31,7 +32,10 @@ internal fun <T : Number> Sequence<T>.mean(type: KType, skipNA: Boolean = skipNA

Number::class -> (this as Sequence<Number>).map { it.toDouble() }.mean(skipNA)

else -> throw IllegalArgumentException("Unable to compute mean for type $type")
// this means the sequence is empty
Nothing::class -> Double.NaN

else -> throw IllegalArgumentException("Unable to compute the mean for type ${renderType(type)}")
}
}

Expand Down
13 changes: 7 additions & 6 deletions core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/math/std.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.dataframe.math

import org.jetbrains.kotlinx.dataframe.api.ddof_default
import org.jetbrains.kotlinx.dataframe.api.skipNA_default
import org.jetbrains.kotlinx.dataframe.impl.renderType
import java.math.BigDecimal
import kotlin.reflect.KType
import kotlin.reflect.full.withNullability
Expand All @@ -13,11 +14,10 @@ internal fun <T : Number> Iterable<T?>.std(
ddof: Int = ddof_default,
): Double {
if (type.isMarkedNullable) {
if (skipNA) {
return filterNotNull().std(type.withNullability(false), true, ddof)
} else {
if (contains(null)) return Double.NaN
return std(type.withNullability(false), skipNA, ddof)
return when {
skipNA -> filterNotNull().std(type = type.withNullability(false), skipNA = true, ddof = ddof)
contains(null) -> Double.NaN
else -> std(type = type.withNullability(false), skipNA = false, ddof = ddof)
}
}
return when (type.classifier) {
Expand All @@ -26,7 +26,8 @@ internal fun <T : Number> Iterable<T?>.std(
Int::class, Short::class, Byte::class -> (this as Iterable<Int>).std(ddof)
Long::class -> (this as Iterable<Long>).std(ddof)
BigDecimal::class -> (this as Iterable<BigDecimal>).std(ddof)
else -> throw IllegalArgumentException("Unsupported type ${type.classifier}")
Nothing::class -> Double.NaN
else -> throw IllegalArgumentException("Unable to compute the std for type ${renderType(type)}")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.jetbrains.kotlinx.dataframe.api

import io.kotest.matchers.shouldBe
import org.jetbrains.kotlinx.dataframe.impl.nothingType
import org.jetbrains.kotlinx.dataframe.type
import org.junit.Test

class ConstructorsTests {
Expand All @@ -24,4 +26,10 @@ class ConstructorsTests {
df.columnsCount() shouldBe 2
df.columnNames() shouldBe listOf(column.name(), "${column.name()}1")
}

@Test
fun `dataFrameOf with nothing columns`() {
dataFrameOf("a" to emptyList())["a"].type shouldBe nothingType(false)
dataFrameOf("a" to listOf(null))["a"].type shouldBe nothingType(true)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.jetbrains.kotlinx.dataframe.statistics

import io.kotest.matchers.shouldBe
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.api.columnOf
import org.jetbrains.kotlinx.dataframe.api.mean
import org.jetbrains.kotlinx.dataframe.impl.nothingType
import org.junit.Test
import kotlin.reflect.typeOf

Expand All @@ -18,5 +20,8 @@ class BasicMathTests {
fun `mean with nans and nulls`() {
columnOf(10, 20, Double.NaN, null).mean() shouldBe Double.NaN
columnOf(10, 20, Double.NaN, null).mean(skipNA = true) shouldBe 15

DataColumn.createValueColumn("", emptyList<Nothing>(), nothingType(false)).mean() shouldBe Double.NaN
DataColumn.createValueColumn("", listOf(null), nothingType(true)).mean() shouldBe Double.NaN
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.jetbrains.kotlinx.dataframe.statistics

import io.kotest.matchers.shouldBe
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.api.columnOf
import org.jetbrains.kotlinx.dataframe.api.columnTypes
import org.jetbrains.kotlinx.dataframe.api.dataFrameOf
import org.jetbrains.kotlinx.dataframe.api.std
import org.jetbrains.kotlinx.dataframe.impl.nothingType
import org.jetbrains.kotlinx.dataframe.math.std
import org.jetbrains.kotlinx.dataframe.type
import org.junit.Test
import kotlin.reflect.typeOf

Expand Down Expand Up @@ -37,4 +40,16 @@ class StdTests {
df[value].std() shouldBe expected
df.std { value } shouldBe expected
}

@Test
fun `std on empty or nullable column`() {
val empty = DataColumn.createValueColumn("", emptyList<Nothing>(), nothingType(false))
val nullable = DataColumn.createValueColumn("", listOf(null), nothingType(true))

empty.values().std(empty.type) shouldBe Double.NaN
nullable.values().std(nullable.type) shouldBe Double.NaN

empty.std() shouldBe Double.NaN
nullable.std() shouldBe Double.NaN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import org.jetbrains.kotlinx.dataframe.AnyFrame
import org.jetbrains.kotlinx.dataframe.AnyRow
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.DataFrame
import org.jetbrains.kotlinx.dataframe.DataRow
import org.jetbrains.kotlinx.dataframe.RowExpression
Expand Down Expand Up @@ -2196,6 +2197,9 @@ class DataFrameTests : BaseTest() {
fun `isNumber`() {
typed.age.isNumber() shouldBe true
typed.weight.isNumber() shouldBe true

DataColumn.createValueColumn("a", emptyList<Nothing>(), nothingType(false)).isNumber() shouldBe true
DataColumn.createValueColumn("a", listOf(null), nothingType(true)).isNumber() shouldBe true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ class UtilTests {

@Test
fun `createType test`() {
emptyList<KClass<*>>().createType(nullable = false) shouldBe typeOf<Any>()
emptyList<KClass<*>>().createType(nullable = true) shouldBe typeOf<Any?>()
emptyList<KClass<*>>().createType(nullable = false) shouldBe nothingType(nullable = false)
emptyList<KClass<*>>().createType(nullable = true) shouldBe nothingType(nullable = true)

listOf(Nothing::class).createType(nullable = false) shouldBe nothingType(nullable = false)
listOf(Nothing::class).createType(nullable = true) shouldBe nothingType(nullable = true)
Expand All @@ -111,6 +111,9 @@ class UtilTests {

listOf(Nothing::class).commonType(false) shouldBe nothingType(nullable = false)
listOf(Nothing::class).commonType(true) shouldBe nothingType(nullable = true)

emptyList<KClass<*>>().commonType(false, null) shouldBe nothingType(nullable = false)
emptyList<KClass<*>>().commonType(true, null) shouldBe nothingType(nullable = true)
}

val a = listOf(1, 2.0, "a")
Expand All @@ -133,6 +136,7 @@ class UtilTests {
guessValueType(sequenceOf(1, 2.0, "a", null, listOf(1, 2))) shouldBe typeOf<Any?>()

guessValueType(sequenceOf(null, null)) shouldBe nothingType(nullable = true)
guessValueType(emptySequence()) shouldBe nothingType(nullable = false)

guessValueType(sequenceOf(listOf<Int?>(null))) shouldBe typeOf<List<Nothing?>>()
guessValueType(sequenceOf(emptyList<Int>())) shouldBe typeOf<List<Nothing>>()
Expand Down
Loading