diff --git a/core/api/core.api b/core/api/core.api index a2ad0c0dcb..bb76090655 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -5304,12 +5304,10 @@ public abstract interface class org/jetbrains/kotlinx/dataframe/columns/ValueCol public synthetic fun distinct ()Lorg/jetbrains/kotlinx/dataframe/DataColumn; public synthetic fun distinct ()Lorg/jetbrains/kotlinx/dataframe/columns/BaseColumn; public abstract fun distinct ()Lorg/jetbrains/kotlinx/dataframe/columns/ValueColumn; - public synthetic fun get (Ljava/lang/Iterable;)Lorg/jetbrains/kotlinx/dataframe/DataColumn; + public abstract fun get (Ljava/lang/Iterable;)Lorg/jetbrains/kotlinx/dataframe/DataColumn; public synthetic fun get (Ljava/lang/Iterable;)Lorg/jetbrains/kotlinx/dataframe/columns/BaseColumn; - public abstract fun get (Ljava/lang/Iterable;)Lorg/jetbrains/kotlinx/dataframe/columns/ValueColumn; - public synthetic fun get (Lkotlin/ranges/IntRange;)Lorg/jetbrains/kotlinx/dataframe/DataColumn; + public abstract fun get (Lkotlin/ranges/IntRange;)Lorg/jetbrains/kotlinx/dataframe/DataColumn; public synthetic fun get (Lkotlin/ranges/IntRange;)Lorg/jetbrains/kotlinx/dataframe/columns/BaseColumn; - public abstract fun get (Lkotlin/ranges/IntRange;)Lorg/jetbrains/kotlinx/dataframe/columns/ValueColumn; public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/DataColumn; public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/columns/BaseColumn; public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/columns/ColumnReference; diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/reverse.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/reverse.kt index 3e3f66325f..50daf141e5 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/reverse.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/reverse.kt @@ -5,6 +5,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.columns.FrameColumn import org.jetbrains.kotlinx.dataframe.columns.ValueColumn +import org.jetbrains.kotlinx.dataframe.impl.columns.asValueColumn import org.jetbrains.kotlinx.dataframe.indices public fun DataFrame.reverse(): DataFrame = get(indices.reversed()) @@ -15,4 +16,4 @@ public fun ColumnGroup.reverse(): ColumnGroup = get(indices.reversed() public fun FrameColumn.reverse(): FrameColumn = get(indices.reversed()) -public fun ValueColumn.reverse(): ValueColumn = get(indices.reversed()) +public fun ValueColumn.reverse(): ValueColumn = get(indices.reversed()).asValueColumn() diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/columns/ValueColumn.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/columns/ValueColumn.kt index e3a0d9b719..6e925067a2 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/columns/ValueColumn.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/columns/ValueColumn.kt @@ -17,14 +17,14 @@ public interface ValueColumn : DataColumn { override fun distinct(): ValueColumn - override fun get(indices: Iterable): ValueColumn + override fun get(indices: Iterable): DataColumn override fun rename(newName: String): ValueColumn override operator fun getValue(thisRef: Any?, property: KProperty<*>): ValueColumn = super.getValue(thisRef, property) as ValueColumn - public override operator fun get(range: IntRange): ValueColumn + public override operator fun get(range: IntRange): DataColumn /** * Changes column [type][BaseColumn.type]. diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/columns/ValueColumnImpl.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/columns/ValueColumnImpl.kt index 97b855be9e..5c74ee78ce 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/columns/ValueColumnImpl.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/columns/ValueColumnImpl.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.dataframe.impl.columns import org.jetbrains.kotlinx.dataframe.AnyFrame import org.jetbrains.kotlinx.dataframe.AnyRow import org.jetbrains.kotlinx.dataframe.DataColumn +import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.columns.ColumnResolutionContext import org.jetbrains.kotlinx.dataframe.columns.ValueColumn @@ -78,12 +79,17 @@ internal open class ValueColumnImpl( override fun addParent(parent: ColumnGroup<*>): DataColumn = ValueColumnWithParent(parent, this) - override fun createWithValues(values: List, hasNulls: Boolean?): ValueColumn { + override fun createWithValues(values: List, hasNulls: Boolean?): DataColumn { val nulls = hasNulls ?: values.any { it == null } + + // If this value column was of type `AnyFrame?` and all nulls are filtered out, return a frame column instead + if (!nulls && type.isSubtypeOf(typeOf())) { + return DataColumn.createFrameColumn(name, values as List).cast() + } return DataColumn.createValueColumn(name, values, type.withNullability(nulls)) } - override fun get(indices: Iterable): ValueColumn { + override fun get(indices: Iterable): DataColumn { var nullable = false val newValues = indices.map { val value = values[it] @@ -96,7 +102,7 @@ internal open class ValueColumnImpl( override fun get(columnName: String) = throw UnsupportedOperationException("Can not get nested column '$columnName' from ValueColumn '$name'") - override operator fun get(range: IntRange): ValueColumn = super.get(range) as ValueColumn + override operator fun get(range: IntRange): DataColumn = super.get(range) as DataColumn override fun defaultValue() = defaultValue diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/columns/DataColumns.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/columns/DataColumns.kt index 4d4e2c1f1e..ca5e9580a4 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/columns/DataColumns.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/columns/DataColumns.kt @@ -1,17 +1,29 @@ package org.jetbrains.kotlinx.dataframe.columns +import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import org.jetbrains.kotlinx.dataframe.AnyFrame import org.jetbrains.kotlinx.dataframe.DataColumn +import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.api.dataFrameOf +import org.jetbrains.kotlinx.dataframe.api.dropNulls +import org.jetbrains.kotlinx.dataframe.api.emptyDataFrame +import org.jetbrains.kotlinx.dataframe.api.take import org.jetbrains.kotlinx.dataframe.api.toColumn import org.jetbrains.kotlinx.dataframe.api.toDataFrame +import org.jetbrains.kotlinx.dataframe.api.update +import org.jetbrains.kotlinx.dataframe.api.where +import org.jetbrains.kotlinx.dataframe.api.with import org.jetbrains.kotlinx.dataframe.core.BuildConfig +import org.jetbrains.kotlinx.dataframe.samples.api.TestBase.Person +import org.jetbrains.kotlinx.dataframe.testSets.person.BaseTest import org.junit.Test import java.net.URI +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.typeOf -class DataColumns { +class DataColumns : BaseTest() { @Test fun `create column with platform type from Api`() { val df1 = listOf(1, 2, 3).toDataFrame { @@ -61,4 +73,51 @@ class DataColumns { ) } } + + // Tests for issue #1926 + val nullableDfValueColumnDf = dataFrameOf( + DataColumn.createValueColumn("col", listOf(df, null)), + ) + + @Test + fun `checking nullableDfValueColumnDf creation`() { + nullableDfValueColumnDf["col"].let { + it.kind() shouldBe ColumnKind.Value + it.type().isSubtypeOf(typeOf?>()) shouldBe true + } + } + + @Test + fun `test removing nulls update from nullable DF ValueCol should turn into FrameCol`() { + val updatedDf = nullableDfValueColumnDf + .update("col").where { it == null }.with { emptyDataFrame() } + updatedDf["col"].let { + it.kind() shouldBe ColumnKind.Frame + it.type().isSubtypeOf(typeOf>()) shouldBe true + } + } + + @Test + fun `test dropNulls from nullable DF ValueCol should turn into FrameCol`() { + shouldNotThrowAny { + nullableDfValueColumnDf["col"].dropNulls() + } + val droppedDf = nullableDfValueColumnDf.dropNulls("col") + droppedDf["col"].let { + it.kind() shouldBe ColumnKind.Frame + it.type().isSubtypeOf(typeOf>()) shouldBe true + } + } + + @Test + fun `test take from nullable DF ValueCol should turn into FrameCol`() { + shouldNotThrowAny { + nullableDfValueColumnDf["col"].take(1) + } + val takenDf = nullableDfValueColumnDf.take(1) + takenDf["col"].let { + it.kind() shouldBe ColumnKind.Frame + it.type().isSubtypeOf(typeOf>()) shouldBe true + } + } }