Skip to content
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 @@ -148,7 +148,13 @@ public fun <T> DataColumn<T>.valueCounts(
}
if (dropNA) grouped = grouped.filter { !it.first.isNA }
val nulls = if (dropNA) false else hasNulls()
val values = DataColumn.createByType(name(), grouped.map { it.first }, type().withNullability(nulls))
val values =
if (grouped.isEmpty() && !isValueColumn()) {
// #2034 Preserve nested columns even when the input is empty
this.take(0)
} else {
DataColumn.createByType(name(), grouped.map { it.first }, type().withNullability(nulls))
}
val countName = if (resultColumn == name()) resultColumn + "1" else resultColumn
val counts = DataColumn.createByType(countName, grouped.map { it.second }, typeOf<Int>())
return dataFrameOf(values, counts).cast()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.kotlinx.dataframe.api

import io.kotest.matchers.shouldBe
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.nrow
import org.junit.Test
import kotlin.reflect.typeOf
Expand Down Expand Up @@ -104,6 +105,30 @@ class ValueCountsTests {
result.rowsCount() shouldBe 0
}

@Test
fun `value counts of an empty nullable value column preserves type when NA values are included`() {
val input = DataColumn.createByType("city", emptyList<String?>(), typeOf<String?>())
val result = input.valueCounts(dropNA = false)

result.columnNames() shouldBe listOf("city", "count")
result.nrow shouldBe 0
result["city"].isValueColumn() shouldBe true
result["city"].type() shouldBe typeOf<String?>()
result["count"].type() shouldBe typeOf<Int>()
}

@Test
fun `value counts of an empty nullable value column drops nullability when NA values are excluded`() {
val input = DataColumn.createByType("city", emptyList<String?>(), typeOf<String?>())
val result = input.valueCounts()

result.columnNames() shouldBe listOf("city", "count")
result.nrow shouldBe 0
result["city"].isValueColumn() shouldBe true
result["city"].type() shouldBe typeOf<String>()
result["count"].type() shouldBe typeOf<Int>()
}

@Test
fun `value counts of a column with a result column name clashing with the column name`() {
val count by columnOf("a", "b", "a")
Expand Down Expand Up @@ -265,5 +290,50 @@ class ValueCountsTests {
df.drop(df.nrow).valueCounts().count() shouldBe 0
}

@Test
fun `value counts of an empty DataFrame retains all columns`() {
val result = df.take(0).valueCounts()

result.columnNames() shouldBe listOf("name", "city", "age", "count")
result.nrow shouldBe 0
result["name"].type() shouldBe typeOf<String>()
result["age"].type() shouldBe typeOf<Int>()
result["count"].type() shouldBe typeOf<Int>()
}

@Test
fun `value counts of an empty DataFrame retains selected columns`() {
val result = df.take(0).valueCounts("city")

result.columnNames() shouldBe listOf("city", "count")
result.nrow shouldBe 0
result["city"].type() shouldBe typeOf<String>()
result["count"].type() shouldBe typeOf<Int>()
}

@Test
fun `value counts retains selected columns when every row is dropped`() {
val input = dataFrameOf("city" to columnOf("London", null)).drop(1)
val result = input.valueCounts("city")

result.columnNames() shouldBe listOf("city", "count")
result.nrow shouldBe 0
result["city"].type() shouldBe typeOf<String>()
result["count"].type() shouldBe typeOf<Int>()
}

@Test
fun `value counts of an empty column group retains its observed columns`() {
val input = df.group("name", "city").into("details").take(0)
val result = input["details"].valueCounts()

result.columnNames() shouldBe listOf("details", "count")
result["details"].asColumnGroup().columnNames() shouldBe listOf("name", "city")
result.nrow shouldBe 0
result["details"].asColumnGroup()["name"].type() shouldBe typeOf<String>()
result["details"].asColumnGroup()["city"].type() shouldBe typeOf<String>()
result["count"].type() shouldBe typeOf<Int>()
}

// endregion
}
Loading