diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt index f88ccb65e5..84c4b1b805 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt @@ -1,17 +1,147 @@ package org.jetbrains.kotlinx.dataframe.api +import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.DataRow import org.jetbrains.kotlinx.dataframe.RowFilter import org.jetbrains.kotlinx.dataframe.columns.FrameColumn +import org.jetbrains.kotlinx.dataframe.documentation.AccessApis +import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls +import org.jetbrains.kotlinx.dataframe.documentation.ExcludeFromSources import org.jetbrains.kotlinx.dataframe.impl.api.duplicateImpl import org.jetbrains.kotlinx.dataframe.impl.api.duplicateRowsImpl -public fun DataFrame.duplicate(n: Int): FrameColumn = List(n) { this }.toFrameColumn() +/** + * @param [n\] The number of copies in the result, the original included. Must be greater than 0. + * @throws [IllegalArgumentException] if [n\] is 0 or negative. + */ +@ExcludeFromSources +private interface DuplicateCountDocs +/** + * In the example, `df` has the columns `name` and `age` and three rows: + * `Alice, 15`, `Bob, 20` and `Charlie, 30`. + */ +@ExcludeFromSources +private interface DuplicateExampleDataDocs + +/** + * Returns a [FrameColumn] that holds this [DataFrame] [n] times. + * + * Each of the [n] cells of the column is this [DataFrame]. + * The column has an empty name. + * + * For more information: {@include [DocumentationUrls.DuplicateOnDataFrame]} + * + * See also: + * - [duplicateRows][DataFrame.duplicateRows] — repeats every row of this [DataFrame] [n] times. + * - [concat][DataColumn.concat] — joins the copies into one [DataFrame]: + * `df.duplicate(2).concat()` has all rows of `df`, followed by all rows of `df` again. + * + * ### Example + * @include [DuplicateExampleDataDocs] + * {@comment Backed by `KDoc example - DataFrame duplicate…` in DuplicateTests. } + * ```kotlin + * // A column with 3 cells, each cell holds `df` + * df.duplicate(3) + * ``` + * + * @include [DuplicateCountDocs] + * @return A [FrameColumn] with an empty name and [n] cells, each holding this [DataFrame]. + */ +public fun DataFrame.duplicate(n: Int): FrameColumn = duplicateImpl(n) + +/** + * Returns a [DataFrame] where every row is repeated [n] times. + * + * The copies of a row come right after it, so the order of the rows stays the same: + * with [n] = 2, rows `A, B` become `A, A, B, B`. + * Values in column groups and frame columns are repeated together with their rows. + * The column names and types stay the same. + * + * Don't confuse this with [distinct][DataFrame.distinct], which removes repeated rows. + * + * For more information: {@include [DocumentationUrls.DuplicateRows]} + * + * See also: + * - [duplicateRows][DataFrame.duplicateRows]`(n) { filter: RowFilter }` — repeats only the rows + * that match a condition. + * - [duplicate][DataFrame.duplicate] — puts copies of the whole [DataFrame] into a [FrameColumn]. + * - [duplicate][DataRow.duplicate] — makes a [DataFrame] out of copies of a single [DataRow]. + * + * ### Example + * @include [DuplicateExampleDataDocs] + * {@comment Backed by `KDoc example - duplicateRows…` in DuplicateTests. } + * ```kotlin + * // Alice, Alice, Alice, Bob, Bob, Bob, Charlie, Charlie, Charlie + * df.duplicateRows(3) + * ``` + * + * @include [DuplicateCountDocs] + * @return A [DataFrame] with every row repeated [n] times. + */ public fun DataFrame.duplicateRows(n: Int): DataFrame = duplicateRowsImpl(n) +/** + * Returns a [DataFrame] where each row that matches the [filter] is repeated [n] times. + * + * Rows that do not match the [filter] appear once, as before. + * The copies of a row come right after it, so the order of the rows stays the same. + * Values in column groups and frame columns are repeated together with their rows. + * The column names and types stay the same. + * + * The [filter] is a [RowFilter] — a lambda that receives each [DataRow] as both `this` and `it` + * and returns `true` for the rows to repeat. + * + * It allows you to define conditions using the row's values directly, + * including through [extension properties][AccessApis.ExtensionPropertiesApi] + * for convenient and type-safe access. + * + * For more information: {@include [DocumentationUrls.DuplicateRows]} {@include [DocumentationUrls.DataRow.RowFilter]} + * + * See also: + * - [duplicateRows][DataFrame.duplicateRows]`(n: Int)` — repeats every row. + * - [filter][DataFrame.filter] — keeps only the rows that match a condition. + * + * ### Example + * @include [DuplicateExampleDataDocs] + * {@comment Backed by `KDoc example - duplicateRows with a condition…` in DuplicateTests. } + * ```kotlin + * // Alice, Bob, Bob, Bob, Charlie, Charlie, Charlie + * df.duplicateRows(3) { age > 18 } + * ``` + * + * @param [filter] The condition that selects the rows to repeat. + * @include [DuplicateCountDocs] + * @return A [DataFrame] where the rows that match the [filter] are repeated [n] times + * and the other rows appear once. + */ public inline fun DataFrame.duplicateRows(n: Int, filter: RowFilter): DataFrame = duplicateRowsImpl(n, rows().filter { filter(it, it) }.map { it.index() }) +/** + * Returns a [DataFrame] with [n] rows, each of them a copy of this [DataRow]. + * + * The result has the same columns as the [DataFrame] this row comes from, + * column groups and frame columns included. + * The type of each column follows the value in this row: + * a nullable column whose value in this row is not `null` becomes non-nullable in the result. + * + * For more information: {@include [DocumentationUrls.DuplicateOnDataRow]} + * + * See also: + * - [duplicateRows][DataFrame.duplicateRows] — repeats rows inside a [DataFrame]. + * - [duplicate][DataFrame.duplicate] — puts copies of a whole [DataFrame] into a [FrameColumn]. + * + * ### Example + * @include [DuplicateExampleDataDocs] + * {@comment Backed by `KDoc example - DataRow duplicate…` in DuplicateTests. } + * ```kotlin + * // Bob, Bob, Bob + * df[1].duplicate(3) + * ``` + * + * @include [DuplicateCountDocs] + * @return A [DataFrame] with [n] copies of this [DataRow]. + */ public fun DataRow.duplicate(n: Int): DataFrame = duplicateImpl(n) 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 dbae32a286..549ed1a111 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 @@ -107,6 +107,15 @@ public interface DocumentationUrls { /** [See `distinctBy` on the documentation website.]({@include [Url]}/distinct.html#distinctby) */ public typealias DistinctBy = Nothing + /** [See `duplicateRows` on the documentation website.]({@include [Url]}/duplicate.html#duplicaterows) */ + public typealias DuplicateRows = Nothing + + /** [See `duplicate` on a DataRow on the documentation website.]({@include [Url]}/duplicate.html#duplicate-on-a-datarow) */ + public typealias DuplicateOnDataRow = Nothing + + /** [See `duplicate` on a DataFrame on the documentation website.]({@include [Url]}/duplicate.html#duplicate-on-a-dataframe) */ + public typealias DuplicateOnDataFrame = Nothing + /** See `flatten` on the documentation website. */ public typealias Flatten = Nothing diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/duplicate.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/duplicate.kt index d3ac7f37f3..73cde1a470 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/duplicate.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/duplicate.kt @@ -11,21 +11,33 @@ import org.jetbrains.kotlinx.dataframe.api.forEachIndexed import org.jetbrains.kotlinx.dataframe.api.isFrameColumn import org.jetbrains.kotlinx.dataframe.api.name import org.jetbrains.kotlinx.dataframe.api.toDataFrame +import org.jetbrains.kotlinx.dataframe.api.toFrameColumn import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup +import org.jetbrains.kotlinx.dataframe.columns.FrameColumn import org.jetbrains.kotlinx.dataframe.impl.owner import org.jetbrains.kotlinx.dataframe.index import org.jetbrains.kotlinx.dataframe.type import kotlin.reflect.full.withNullability -internal fun DataFrame.duplicateRowsImpl(n: Int): DataFrame = - columns() +private fun requirePositiveDuplicates(n: Int) { + require(n > 0) { "Number of duplicates must be greater than 0, but was $n" } +} + +internal fun DataFrame.duplicateImpl(n: Int): FrameColumn { + requirePositiveDuplicates(n) + return List(n) { this }.toFrameColumn() +} + +internal fun DataFrame.duplicateRowsImpl(n: Int): DataFrame { + requirePositiveDuplicates(n) + return columns() .map { it.duplicateValuesImpl(n) } .toDataFrame() .cast() +} -internal fun DataColumn.duplicateValuesImpl(n: Int): DataColumn { - require(n > 0) { "Number of duplicates must be greater than 0, but was $n" } - return when (this) { +internal fun DataColumn.duplicateValuesImpl(n: Int): DataColumn = + when (this) { is ColumnGroup<*> -> DataColumn .createColumnGroup( @@ -42,7 +54,6 @@ internal fun DataColumn.duplicateValuesImpl(n: Int): DataColumn { } } }.cast() -} internal fun DataColumn.duplicateValuesImpl(n: Int, indicesSorted: Iterable): DataColumn = when (this) { @@ -73,14 +84,17 @@ internal fun DataColumn.duplicateValuesImpl(n: Int, indicesSorted: Iterab }.cast() @PublishedApi -internal fun DataFrame.duplicateRowsImpl(n: Int, indicesSorted: Iterable): DataFrame = - columns() +internal fun DataFrame.duplicateRowsImpl(n: Int, indicesSorted: Iterable): DataFrame { + requirePositiveDuplicates(n) + return columns() .map { it.duplicateValuesImpl(n, indicesSorted) } .toDataFrame() .cast() +} -internal fun DataRow.duplicateImpl(n: Int): DataFrame = - owner.columns().map { col -> +internal fun DataRow.duplicateImpl(n: Int): DataFrame { + requirePositiveDuplicates(n) + return owner.columns().map { col -> when (col) { is ColumnGroup<*> -> DataColumn.createColumnGroup(col.name, col[index].duplicateImpl(n)) @@ -98,3 +112,4 @@ internal fun DataRow.duplicateImpl(n: Int): DataFrame = } } }.toDataFrame().cast() +} diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt new file mode 100644 index 0000000000..48af1dc468 --- /dev/null +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/duplicate.kt @@ -0,0 +1,192 @@ +package org.jetbrains.kotlinx.dataframe.api + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import org.jetbrains.kotlinx.dataframe.DataFrame +import org.jetbrains.kotlinx.dataframe.columns.FrameColumn +import org.junit.Test +import kotlin.reflect.typeOf + +@Suppress("ktlint:standard:argument-list-wrapping") +class DuplicateTests { + + private val df = dataFrameOf("name", "age")( + "Alice", 15, + "Bob", 20, + "Charlie", 30, + ) + + // region duplicateRows + + @Test + fun `KDoc example - duplicateRows repeats every row n times, each copy next to its original`() { + df.duplicateRows(3) shouldBe dataFrameOf("name", "age")( + "Alice", 15, + "Alice", 15, + "Alice", 15, + "Bob", 20, + "Bob", 20, + "Bob", 20, + "Charlie", 30, + "Charlie", 30, + "Charlie", 30, + ) + } + + @Test + fun `KDoc example - duplicateRows with a condition repeats only matching rows and keeps the other rows once`() { + df.duplicateRows(3) { "age"() > 18 } shouldBe dataFrameOf("name", "age")( + "Alice", 15, + "Bob", 20, + "Bob", 20, + "Bob", 20, + "Charlie", 30, + "Charlie", 30, + "Charlie", 30, + ) + } + + @Test + fun `duplicateRows repeats column groups and frame columns together with their rows`() { + val one = dataFrameOf("x")(1) + val two = dataFrameOf("x")(2, 3) + val nested = dataFrameOf("name", "age")("Alice", 15, "Bob", 20) + .group("name", "age").into("person") + .add(columnOf(one, two).rename("frames")) + + nested.duplicateRows(2) shouldBe dataFrameOf("name", "age")("Alice", 15, "Alice", 15, "Bob", 20, "Bob", 20) + .group("name", "age").into("person") + .add(columnOf(one, one, two, two).rename("frames")) + + nested.duplicateRows(2) { "person"["name"]() == "Bob" } shouldBe + dataFrameOf("name", "age")("Alice", 15, "Bob", 20, "Bob", 20) + .group("name", "age").into("person") + .add(columnOf(one, two, two).rename("frames")) + } + + @Test + fun `duplicateRows keeps column names and types, nullable ones included`() { + val nullable = dataFrameOf("a", "b")( + 1, null, + null, "x", + ) + + val duplicated = nullable.duplicateRows(2) + + duplicated.columnNames() shouldBe listOf("a", "b") + duplicated["a"].type() shouldBe typeOf() + duplicated["b"].type() shouldBe typeOf() + // the overload with a condition keeps the types too + nullable.duplicateRows(2) { index() == 0 }["a"].type() shouldBe typeOf() + } + + @Test + fun `duplicateRows rejects n that is not positive`() { + shouldThrow { df.duplicateRows(0) }.message shouldBe + "Number of duplicates must be greater than 0, but was 0" + shouldThrow { df.duplicateRows(-1) }.message shouldBe + "Number of duplicates must be greater than 0, but was -1" + // a dataframe without columns has no column to check n against + shouldThrow { DataFrame.empty(3).duplicateRows(0) }.message shouldBe + "Number of duplicates must be greater than 0, but was 0" + } + + @Test + fun `duplicateRows with a condition rejects n that is not positive instead of removing matching rows`() { + shouldThrow { df.duplicateRows(0) { "age"() > 18 } }.message shouldBe + "Number of duplicates must be greater than 0, but was 0" + shouldThrow { df.duplicateRows(-1) { "age"() > 18 } }.message shouldBe + "Number of duplicates must be greater than 0, but was -1" + } + + @Test + fun `n = 1 keeps one copy of everything, the original included`() { + df.duplicateRows(1) shouldBe df + df.duplicateRows(1) { "age"() > 18 } shouldBe df + df[1].duplicate(1) shouldBe dataFrameOf("name", "age")("Bob", 20) + df.duplicate(1).values() shouldBe listOf(df) + } + + // endregion + + // region DataRow.duplicate + + @Test + fun `KDoc example - DataRow duplicate returns a dataframe with the row repeated n times`() { + df[1].duplicate(3) shouldBe dataFrameOf("name", "age")( + "Bob", 20, + "Bob", 20, + "Bob", 20, + ) + } + + @Test + fun `DataRow duplicate repeats values of column groups and frame columns`() { + val one = dataFrameOf("x")(1) + val two = dataFrameOf("x")(2, 3) + val nested = dataFrameOf("name", "age")("Alice", 15, "Bob", 20) + .group("name", "age").into("person") + .add(columnOf(one, two).rename("frames")) + + nested[1].duplicate(2) shouldBe dataFrameOf("name", "age")("Bob", 20, "Bob", 20) + .group("name", "age").into("person") + .add(columnOf(two, two).rename("frames")) + } + + @Test + fun `DataRow duplicate makes a nullable column non-nullable when the value in the row is not null`() { + val nullable = dataFrameOf("a", "b")( + 1, null, + null, "x", + ) + nullable["a"].type() shouldBe typeOf() + nullable["b"].type() shouldBe typeOf() + + val duplicated = nullable[0].duplicate(2) + + duplicated["a"].type() shouldBe typeOf() + duplicated["b"].type() shouldBe typeOf() + } + + @Test + fun `DataRow duplicate rejects n that is not positive`() { + shouldThrow { df[1].duplicate(0) }.message shouldBe + "Number of duplicates must be greater than 0, but was 0" + shouldThrow { df[1].duplicate(-1) }.message shouldBe + "Number of duplicates must be greater than 0, but was -1" + } + + // endregion + + // region DataFrame.duplicate + + @Test + fun `KDoc example - DataFrame duplicate returns an unnamed frame column with n copies of the dataframe`() { + val copies: FrameColumn<*> = df.duplicate(3) + + copies.name() shouldBe "" + copies.values() shouldBe listOf(df, df, df) + } + + @Test + fun `KDoc example - DataFrame duplicate with concat has all rows followed by all rows again`() { + df.duplicate(2).concat() shouldBe dataFrameOf("name", "age")( + "Alice", 15, + "Bob", 20, + "Charlie", 30, + "Alice", 15, + "Bob", 20, + "Charlie", 30, + ) + } + + @Test + fun `DataFrame duplicate rejects n that is not positive`() { + shouldThrow { df.duplicate(0) }.message shouldBe + "Number of duplicates must be greater than 0, but was 0" + shouldThrow { df.duplicate(-1) }.message shouldBe + "Number of duplicates must be greater than 0, but was -1" + } + + // endregion +} diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateDataFrame.html b/docs/StardustDocs/resources/api/duplicate/duplicateDataFrame.html new file mode 100644 index 0000000000..8ad3822283 --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateDataFrame.html @@ -0,0 +1,528 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateDataFrameConcat.html b/docs/StardustDocs/resources/api/duplicate/duplicateDataFrameConcat.html new file mode 100644 index 0000000000..0134bcc464 --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateDataFrameConcat.html @@ -0,0 +1,511 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateDf.html b/docs/StardustDocs/resources/api/duplicate/duplicateDf.html new file mode 100644 index 0000000000..ce396e7a9b --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateDf.html @@ -0,0 +1,511 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateRow.html b/docs/StardustDocs/resources/api/duplicate/duplicateRow.html new file mode 100644 index 0000000000..2e4460873a --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateRow.html @@ -0,0 +1,511 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateRows.html b/docs/StardustDocs/resources/api/duplicate/duplicateRows.html new file mode 100644 index 0000000000..96788d2c17 --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateRows.html @@ -0,0 +1,511 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/resources/api/duplicate/duplicateRowsWhere_properties.html b/docs/StardustDocs/resources/api/duplicate/duplicateRowsWhere_properties.html new file mode 100644 index 0000000000..10cecbfd9d --- /dev/null +++ b/docs/StardustDocs/resources/api/duplicate/duplicateRowsWhere_properties.html @@ -0,0 +1,511 @@ + + + + + +
+ +

+ + + diff --git a/docs/StardustDocs/topics/_shadow_resources.md b/docs/StardustDocs/topics/_shadow_resources.md index 7d244123b0..00adacb9b5 100644 --- a/docs/StardustDocs/topics/_shadow_resources.md +++ b/docs/StardustDocs/topics/_shadow_resources.md @@ -97,6 +97,12 @@ + + + + + + diff --git a/docs/StardustDocs/topics/appendDuplicate.md b/docs/StardustDocs/topics/appendDuplicate.md index 13653e069c..263b20dde7 100644 --- a/docs/StardustDocs/topics/appendDuplicate.md +++ b/docs/StardustDocs/topics/appendDuplicate.md @@ -1,4 +1,4 @@ [//]: # (title: Append / duplicate rows) * [`append`](append.md) — append new rows -* [`duplicate`](duplicate.md) — duplicate selected rows +* [`duplicate`](duplicate.md) — repeat rows or whole dataframes diff --git a/docs/StardustDocs/topics/duplicate.md b/docs/StardustDocs/topics/duplicate.md index 1728f1f91c..568467a97f 100644 --- a/docs/StardustDocs/topics/duplicate.md +++ b/docs/StardustDocs/topics/duplicate.md @@ -1,20 +1,151 @@ [//]: # (title: duplicate) -Returns [`DataFrame`](DataFrame.md) with original [`DataRow`](DataRow.md) repeated `n` times. + + +Repeats rows or whole dataframes: +[`duplicateRows`](#duplicaterows) repeats rows inside a [`DataFrame`](DataFrame.md), +[`duplicate`](#duplicate-on-a-datarow) on a [`DataRow`](DataRow.md) makes a [`DataFrame`](DataFrame.md) out of copies +of the row, and [`duplicate`](#duplicate-on-a-dataframe) on a [`DataFrame`](DataFrame.md) makes a +[`FrameColumn`](DataColumn.md#framecolumn) out of copies of the whole dataframe. + +**Related operations**: [](appendDuplicate.md) + +The number of copies `n` counts the original too, so `n = 1` gives one copy. +`n` must be greater than 0. +For `n = 0` or a negative `n`, every `duplicate` and `duplicateRows` call throws an `IllegalArgumentException`. + +Every example on this page uses the same [`DataFrame`](DataFrame.md): + + + +```kotlin +df +``` + + + + +## duplicateRows + +Returns a [`DataFrame`](DataFrame.md) where every row is repeated `n` times. +The copies of a row come right after it, so the order of the rows stays the same. +The examples use the `df` shown at the top of this page. + +```text +duplicateRows(n): DataFrame +duplicateRows(n) { rowCondition }: DataFrame +``` + + + +```kotlin +df.duplicateRows(3) +``` + + + + +With a [row condition](DataRow.md#row-conditions), only the rows that match it are repeated. +The other rows appear once, as before: + + + + + +```kotlin +df.duplicateRows(3) { age > 18 } +``` + + + + +```kotlin +df.duplicateRows(3) { "age"() > 18 } +``` + + + + + +Values in column groups and frame columns are repeated together with their rows. +The column names and types stay the same. + +Don't confuse `duplicateRows` with [`distinct`](distinct.md), which removes repeated rows. + +## duplicate on a DataRow + +Returns a [`DataFrame`](DataFrame.md) with `n` rows, each of them a copy of the row. +The example uses the `df` shown at the top of this page. + ```text DataRow.duplicate(n): DataFrame ``` -Returns [`FrameColumn`](DataColumn.md#framecolumn) with original [`DataFrame`](DataFrame.md) repeated `n` times. -Resulting [`FrameColumn`](DataColumn.md#framecolumn) will have an empty [`name`](DataColumn.md#properties). + -**Related operations**: [](appendDuplicate.md) +```kotlin +df[1].duplicate(3) +``` + + + + +The result has the same columns as the dataframe the row comes from, +column groups and frame columns included. +The type of each column follows the value in the row: +a nullable column whose value in the row is not `null` becomes non-nullable in the result. +Here `name` is `String?` in `people`, and `String` in the copies of the first row: + + + +```kotlin +val people = dataFrameOf( + "name" to listOf("Alice", null), + "age" to listOf(15, 20), +) +people[0].duplicate(2).schema() +``` + +Output: ```text -DataFrame.duplicate(n): FrameColumn +name: String +age: Int ``` -Returns [`DataFrame`](DataFrame.md) where rows that satisfy to the given [condition](DataRow.md#row-conditions) are repeated `n` times. If `rowCondition` is not specified all rows will be duplicated. + + +## duplicate on a DataFrame + +Returns a [`FrameColumn`](DataColumn.md#framecolumn) with `n` cells, each holding the whole dataframe. +The column has an empty name. +The example uses the `df` shown at the top of this page. + ```text -DataFrame.duplicateRows(n) [ { rowCondition } ]: DataFrame +DataFrame.duplicate(n): FrameColumn ``` + + + +```kotlin +df.duplicate(3) +``` + + + + +A dataframe gives a column without a name the name `untitled`, so the table above shows it this way. + +To get one [`DataFrame`](DataFrame.md) with all rows of `df`, followed by all rows of `df` again, +[`concat`](concat.md) the copies: + + + +```kotlin +df.duplicate(2).concat() +``` + + + + +**See also**: [`append`](append.md), [`concat`](concat.md), [`distinct`](distinct.md). diff --git a/samples/build.gradle.kts b/samples/build.gradle.kts index f46d942b24..140a86943a 100644 --- a/samples/build.gradle.kts +++ b/samples/build.gradle.kts @@ -124,6 +124,7 @@ korro { include("countDistinct.md") include("fill.md") include("drop.md") + include("duplicate.md") include("filter.md") include("count.md") include("valueCounts.md") diff --git a/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/DuplicateSamples.kt b/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/DuplicateSamples.kt new file mode 100644 index 0000000000..c828969785 --- /dev/null +++ b/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/DuplicateSamples.kt @@ -0,0 +1,119 @@ +package org.jetbrains.kotlinx.dataframe.samples.api + +import io.kotest.matchers.shouldBe +import org.jetbrains.kotlinx.dataframe.DataFrame +import org.jetbrains.kotlinx.dataframe.annotations.DataSchema +import org.jetbrains.kotlinx.dataframe.api.cast +import org.jetbrains.kotlinx.dataframe.api.concat +import org.jetbrains.kotlinx.dataframe.api.dataFrameOf +import org.jetbrains.kotlinx.dataframe.api.duplicate +import org.jetbrains.kotlinx.dataframe.api.duplicateRows +import org.jetbrains.kotlinx.dataframe.api.schema +import org.jetbrains.kotlinx.dataframe.api.toDataFrame +import org.jetbrains.kotlinx.dataframe.samples.DataFrameSampleHelper +import org.junit.Test + +class DuplicateSamples : DataFrameSampleHelper("duplicate", "api") { + + @DataSchema + interface NameAndAge { + val name: String + val age: Int + } + + // the same dataframe as in the KDoc examples of `duplicate` and `duplicateRows` + private val df: DataFrame = dataFrameOf( + "name" to listOf("Alice", "Bob", "Charlie"), + "age" to listOf(15, 20, 30), + ).cast() + + @Test + fun duplicateDf() { + // SampleStart + df + // SampleEnd + .saveDfHtmlSample() + } + + @Test + fun duplicateRows() { + // SampleStart + df.duplicateRows(3) + // SampleEnd + .also { + it.name.toList() shouldBe + listOf("Alice", "Alice", "Alice", "Bob", "Bob", "Bob", "Charlie", "Charlie", "Charlie") + } + .saveDfHtmlSample() + } + + @Test + fun duplicateRowsWhere_properties() { + // SampleStart + df.duplicateRows(3) { age > 18 } + // SampleEnd + .also { + it.name.toList() shouldBe listOf("Alice", "Bob", "Bob", "Bob", "Charlie", "Charlie", "Charlie") + } + .saveDfHtmlSample() + } + + @Test + fun duplicateRowsWhere_strings() { + // SampleStart + df.duplicateRows(3) { "age"() > 18 } + // SampleEnd + .also { + it["name"].toList() shouldBe listOf("Alice", "Bob", "Bob", "Bob", "Charlie", "Charlie", "Charlie") + } + } + + @Test + fun duplicateRow() { + // SampleStart + df[1].duplicate(3) + // SampleEnd + .also { it.name.toList() shouldBe listOf("Bob", "Bob", "Bob") } + .saveDfHtmlSample() + } + + @Test + fun duplicateRowNullable() { + // SampleStart + val people = dataFrameOf( + "name" to listOf("Alice", null), + "age" to listOf(15, 20), + ) + people[0].duplicate(2).schema() + // SampleEnd + .toString() + .also { + people.schema().toString() shouldBe "name: String?\nage: Int" + it shouldBe "name: String\nage: Int" + } + .saveTextSample() + } + + @Test + fun duplicateDataFrame() { + // SampleStart + df.duplicate(3) + // SampleEnd + .also { + it.name() shouldBe "" + it.values() shouldBe listOf(df, df, df) + // a dataframe gives a column without a name the name `untitled`, so the table shows it this way + it.toDataFrame().columnNames() shouldBe listOf("untitled") + } + .saveDfHtmlSample() + } + + @Test + fun duplicateDataFrameConcat() { + // SampleStart + df.duplicate(2).concat() + // SampleEnd + .also { it.name.toList() shouldBe listOf("Alice", "Bob", "Charlie", "Alice", "Bob", "Charlie") } + .saveDfHtmlSample() + } +}