diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt index 5cb7742e16..8e93e6b0a4 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt @@ -10,23 +10,137 @@ import org.jetbrains.kotlinx.dataframe.annotations.Interpretable import org.jetbrains.kotlinx.dataframe.annotations.Refine import org.jetbrains.kotlinx.dataframe.columns.ColumnAccessor import org.jetbrains.kotlinx.dataframe.columns.ColumnPath +import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls +import org.jetbrains.kotlinx.dataframe.documentation.DslGrammarLink +import org.jetbrains.kotlinx.dataframe.documentation.ExcludeFromSources +import org.jetbrains.kotlinx.dataframe.documentation.Indent +import org.jetbrains.kotlinx.dataframe.documentation.LineBreak +import org.jetbrains.kotlinx.dataframe.documentation.SelectingColumns import org.jetbrains.kotlinx.dataframe.impl.api.insertImpl import org.jetbrains.kotlinx.dataframe.impl.columnName import org.jetbrains.kotlinx.dataframe.impl.removeAt import org.jetbrains.kotlinx.dataframe.util.DEPRECATED_ACCESS_API +import org.jetbrains.kotlinx.dataframe.util.INSERT_AFTER_COL_PATH +import org.jetbrains.kotlinx.dataframe.util.INSERT_AFTER_COL_PATH_REPLACE import kotlin.reflect.KProperty // region DataFrame // region insert +/** + * This function does not immediately insert the new column but instead specify a column to insert and + * returns an [InsertClause], + * which serves as an intermediate step. + * The [InsertClause] object provides methods to insert a new column using: + * - [under][InsertClause.under] - inserts a new column under the specified column group. + * - [after][InsertClause.after] - inserts a new column after the specified column. + * - [at][InsertClause.at]- inserts a new column at the specified position. + * + * Each method returns a new [DataFrame] with the inserted column. + * + * Check out [Grammar]. + * + * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] + * + * See [Selecting Columns][InsertSelectingOptions]. + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See also: + * - [move][DataFrame.move] - move columns to a new position within the [DataFrame]. + * - [add][DataFrame.add] - add new columns to the [DataFrame] + * (without specifying a position, to the end of the [DataFrame]). + */ +internal interface InsertDocs { + + /** + * {@comment Version of [SelectingColumns] with correctly filled in examples} + * @include [SelectingColumns] {@include [SetInsertOperationArg]} + */ + interface InsertSelectingOptions + + /** + * ## Insert Operation Grammar + * {@include [LineBreak]} + * {@include [DslGrammarLink]} + * {@include [LineBreak]} + * + * [**`insert`**][insert]**`(`**`column: `[`DataColumn`][DataColumn]**`)`**` /` + * + * [**`insert`**][insert]**`(`**`name: `[`String`][String]**`, `**`infer: `[`Infer`][Infer]**`) { `**`rowExpression: `[`RowExpression`][RowExpression]**` }`** + * + * {@include [Indent]} + * __`.`__[**`under`**][InsertClause.under]**` { `**`column: `[`ColumnSelector`][ColumnSelector]**` }`** + * + * {@include [Indent]} + * `| `__`.`__[**`under`**][InsertClause.under]**`(`**` columnPath: `[`ColumnPath`][ColumnPath]**`)`** + * + * {@include [Indent]} + * `| `__`.`__[**`after`**][InsertClause.after]**` { `**`column: `[`ColumnSelector`][ColumnSelector]**` }`** + * + * {@include [Indent]} + * `| `__`.`__[**`at`**][InsertClause.at]**`(`**`position: `[`Int`][Int]**`)`** + */ + interface Grammar +} + +/** {@set [SelectingColumns.OPERATION] [insert][insert]} */ +@ExcludeFromSources +private interface SetInsertOperationArg + +/** + * Inserts the given [column] into this [DataFrame]. + * + * {@include [InsertDocs]} + * + * ### Examples: + * ```kotlin + * // Insert a new column "age" under the column group with path ("info", "personal"). + * df.insert(age).under(pathOf("info", "personal")) + * + * // Insert a new column "count" after the column "url". + * df.insert(count).after { url } + * ``` + * + * @param [column] A single [DataColumn] to insert into the [DataFrame]. + * @return An [InsertClause] for specifying the placement of the new column. + */ public fun DataFrame.insert(column: DataColumn): InsertClause = InsertClause(this, column) +/** + * Creates a new column using the provided [expression][AddExpression] and inserts it into this [DataFrame]. + * + * {@include [AddExpressionDocs]} + * + * {@include [InsertDocs]} + * + * ## Examples + * + * ```kotlin + * // Insert a new column "sum" that contains the sum of values from the "firstValue" + * // and "secondValue" columns for each row after the "firstValue" column. + * val dfWithSum = df.insert("sum") { firstValue + secondValue }.after { firstValue } + * + * // Insert a new "fibonacci" column with the Fibonacci sequence under a "math" column group: + * // for the first two rows, the value is 1; + * // for subsequent rows, it's the sum of the two previous Fibonacci values. + * val dfWithFibonacci = df.insert("fibonacci") { + * if (index() < 2) 1 + * else prev()!!.newValue() + prev()!!.prev()!!.newValue() + * }.under("math") + * ``` + * + * @param [name] The name of the new column to be created and inserted. + * @param [infer] Controls how values are inferred when building the new column. Defaults to [Infer.Nulls]. + * @param [expression] An [AddExpression] that computes the value for each row of the new column. + * @return An [InsertClause] for specifying the placement of the newly created column. + */ @Interpretable("Insert1") public inline fun DataFrame.insert( name: String, infer: Infer = Infer.Nulls, - noinline expression: RowExpression, + noinline expression: AddExpression, ): InsertClause = insert(mapToColumn(name, infer, expression)) @Deprecated(DEPRECATED_ACCESS_API) @@ -34,7 +148,7 @@ public inline fun DataFrame.insert( public inline fun DataFrame.insert( column: ColumnAccessor, infer: Infer = Infer.Nulls, - noinline expression: RowExpression, + noinline expression: AddExpression, ): InsertClause = insert(column.name(), infer, expression) @Deprecated(DEPRECATED_ACCESS_API) @@ -42,21 +156,84 @@ public inline fun DataFrame.insert( public inline fun DataFrame.insert( column: KProperty, infer: Infer = Infer.Nulls, - noinline expression: RowExpression, + noinline expression: AddExpression, ): InsertClause = insert(column.columnName, infer, expression) // endregion +/** + * An intermediate class used in the [insert] operation. + * + * This class itself does not perform any insertions — it is a transitional step + * before specifying how to insert the selected columns. + * It must be followed by one of the inserting methods + * to produce a new [DataFrame] with an inserted column. + * + * Use the following methods to perform the insertion: + * - [under][InsertClause.under] - inserts a new column under the specified column group. + * - [after][InsertClause.after] - inserts a new column after the specified column. + * - [at][InsertClause.at]- inserts a new column at the specified position. + * + * See [Grammar][InsertDocs.Grammar] for more details. + */ public class InsertClause(internal val df: DataFrame, internal val column: AnyCol) { override fun toString(): String = "InsertClause(df=$df, column=$column)" } // region under +/** + * Inserts the new column previously specified with [insert] under + * the selected [column group][column]. + * + * Works only with existing column groups. + * To insert into a new column group, use the overloads: + * `under(path: ColumnPath)` or `under(column: String)`. + * [Should be fixed](https://github.com/Kotlin/dataframe/issues/1411). + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * See [SelectingColumns.Dsl]. + * + * ### Examples + * ```kotlin + * // Insert a new column "age" under the column group with path ("info", "personal") + * df.insert(age).under { info.personal } + * + * // Insert a new column "sum" under the only top-level column group + * val dfWithSum = df.insert("sum") { a + b }.under { colGroups().single() } + * ``` + * + * @param column The [ColumnSelector] used to choose an existing column group in this [DataFrame] + * under which the new column will be inserted. + * @return A new [DataFrame] with the inserted column placed under the selected group. + */ @Refine @Interpretable("Under0") public fun InsertClause.under(column: ColumnSelector): DataFrame = under(df.getColumnPath(column)) +/** + * Inserts the new column previously specified with [insert] under + * the column group defined by the given [columnPath]. + * + * {@include [org.jetbrains.kotlinx.dataframe.documentation.ColumnPathCreation]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * ### Example + * ```kotlin + * // Insert a new column "age" under the column group with path ("info", "personal") + * df.insert(age).under(pathOf("info", "personal")) + * ``` + * + * @param [columnPath] The [ColumnPath] specifying the path to a column group in this [DataFrame] + * under which the new column will be inserted. + * @return A new [DataFrame] with the inserted column placed under the specified column group. + */ @Refine @Interpretable("Under1") public fun InsertClause.under(columnPath: ColumnPath): DataFrame = @@ -70,6 +247,26 @@ public fun InsertClause.under(column: ColumnAccessor<*>): DataFrame = @AccessApiOverload public fun InsertClause.under(column: KProperty<*>): DataFrame = under(column.columnName) +/** + * Inserts the new column previously specified with [insert] under + * the given column group by its [name][column]. + * + * If the column group with the provided [name][column] does not exist, it will be created automatically. + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * ### Example + * ```kotlin + * // Insert a new column "age" under the "info" column group. + * df.insert(age).under("info") + * ``` + * + * @param [column] The [name][String] of the column group in this [DataFrame]. + * If the group does not exist, it will be created. + * @return A new [DataFrame] with the inserted column placed under the specified column group. + */ @Refine @Interpretable("Under4") public fun InsertClause.under(column: String): DataFrame = under(pathOf(column)) @@ -78,29 +275,98 @@ public fun InsertClause.under(column: String): DataFrame = under(pathO // region after +/** + * Inserts the new column previously specified with [insert] + * at the position immediately after the selected [column] (on the same level). + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * See also: [SelectingColumns.Dsl]. + * + * ### Examples: + * ```kotlin + * // Insert a new column "age" after the "name" column + * df.insert(age).after { name } + * + * // Insert a new column "sum" after the nested "min" column (inside the "stats" column group) + * val dfWithSum = df.insert("sum") { a + b }.after { stats.min } + * ``` + * + * @param [column] The [ColumnSelector] used to choose an existing column in this [DataFrame], + * after which the new column will be inserted. + * @return A new [DataFrame] with the inserted column placed after the selected column. + */ @Refine @Interpretable("InsertAfter0") -public fun InsertClause.after(column: ColumnSelector): DataFrame = after(df.getColumnPath(column)) +public fun InsertClause.after(column: ColumnSelector): DataFrame = afterImpl(df.getColumnPath(column)) +/** + * Inserts the new column previously specified with [insert] + * at the position immediately after the column with the given [name][column]. + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * See also: [SelectingColumns.ColumnNames]. + * + * ### Example + * ```kotlin + * // Insert a new column "age" after the "name" column + * df.insert(age).after("name") + * ``` + * + * @param [column] The [String] name of the column in this [DataFrame] + * after which the new column will be inserted. + * @return A new [DataFrame] with the inserted column placed after the specified column. + */ public fun InsertClause.after(column: String): DataFrame = df.add(this.column).move(this.column).after(column) @Deprecated(DEPRECATED_ACCESS_API) @AccessApiOverload -public fun InsertClause.after(column: ColumnAccessor<*>): DataFrame = after(column.path()) +public fun InsertClause.after(column: ColumnAccessor<*>): DataFrame = afterImpl(column.path()) @Deprecated(DEPRECATED_ACCESS_API) @AccessApiOverload public fun InsertClause.after(column: KProperty<*>): DataFrame = after(column.columnName) +@Deprecated(INSERT_AFTER_COL_PATH, ReplaceWith(INSERT_AFTER_COL_PATH_REPLACE), DeprecationLevel.ERROR) public fun InsertClause.after(columnPath: ColumnPath): DataFrame { val dstPath = ColumnPath(columnPath.removeAt(columnPath.size - 1) + column.name()) return df.insertImpl(dstPath, column).move { dstPath }.after { columnPath } } +internal fun InsertClause.afterImpl(columnPath: ColumnPath): DataFrame { + val dstPath = ColumnPath(columnPath.removeAt(columnPath.size - 1) + column.name()) + return df.insertImpl(dstPath, column).move { dstPath }.after { columnPath } +} + // endregion // region at +/** + * Inserts the new column previously specified with [insert] + * at the given [position] in the [DataFrame]. + * + * The new column will be placed at the specified index, shifting existing columns to the right. + * + * For more information: {@include [DocumentationUrls.Insert]} + * + * See [Grammar][InsertDocs.Grammar] for more details. + * + * ### Example + * ```kotlin + * // Insert a new column "age" at index 3 + * df.insert(age).at(3) + * ``` + * + * @param [position] The [Int] index where the new column should be inserted. + * Columns currently at this index and after will be shifted right. + * @return A new [DataFrame] with the inserted column placed at the specified position. + */ @Refine @Interpretable("InsertAt") public fun InsertClause.at(position: Int): DataFrame = df.add(column).move(column).to(position) 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 780da6c9fb..e6a3882281 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 @@ -135,6 +135,9 @@ internal interface DocumentationUrls { /** [See `format` on the documentation website.]({@include [Url]}/format.html) */ interface Format + /** [See `insert` on the documentation website.]({@include [Url]}/insert.html) */ + interface Insert + /** [See `rename` on the documentation website.]({@include [Url]}/rename.html) */ interface Rename } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt index 0c2833df57..19ad98d655 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt @@ -139,6 +139,10 @@ internal const val COL_TYPE_INSTANT = "kotlinx.datetime.Instant is deprecated in favor of kotlin.time.Instant. Either migrate to kotlin.time.Instant and use ColType.StdlibInstant or use ColType.DeprecatedInstant. $MESSAGE_1_0 and migrated to kotlin.time.Instant in 1.1." internal const val COL_TYPE_INSTANT_REPLACE = "ColType.DeprecatedInstant" +internal const val INSERT_AFTER_COL_PATH = + "This `after()` overload will be removed in favor of `after { }` with Column Selection DSL. $MESSAGE_1_0" +internal const val INSERT_AFTER_COL_PATH_REPLACE = "this.after { columnPath }" + // endregion // region WARNING in 1.0, ERROR in 1.1 diff --git a/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/impl/api/insert.kt b/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/impl/api/insert.kt index 53875a86b5..c34b22727f 100644 --- a/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/impl/api/insert.kt +++ b/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/impl/api/insert.kt @@ -83,7 +83,7 @@ internal class InsertAfter0 : AbstractInterpreter() { override fun Arguments.interpret(): PluginDataFrameSchema { return receiver.df.asDataFrame() - .insert(receiver.column.asDataColumn()).after(column.col.path) + .insert(receiver.column.asDataColumn()).after { column.col.path } .toPluginDataFrameSchema() } }