# Extension Properties API

> For the complete documentation index, see [llms.txt](https://kotlin.github.io/dataframe/llms.txt).

When working with a [DataFrame](dataframe.html), the most convenient and reliable way to access its columns — including for operations and retrieving column values in [row expressions](datarow.html#row-expressions) — is through auto-generated extension properties. They are generated based on a [dataframe schema](schemas.html), with the name and type of properties inferred from the name and type of the corresponding columns. It also works for all types of hierarchical dataframes.

API reference: [DataSchema](https://kotlin.github.io/dataframe/api-docs/core/org.jetbrains.kotlinx.dataframe.annotations/-data-schema/index.html)

> **Warning:**
> The behavior of data schema generation differs between the [Compiler Plugin](compiler-plugin.html) and [Kotlin Notebook](setupkotlinnotebook.html).
>
>
>
> * In Kotlin Notebook, a schema is generated only after cell execution for `DataFrame` variables defined within that cell.
>
> * With the Compiler Plugin, a new schema is generated after every operation — but support for all operations is still in progress. Retrieving the schema for `DataFrame` read from a file or URL is not yet supported either.
>
>
>
> This behavior may change in future releases. See the [example](#example) below that demonstrates these differences.

## Example

Consider a simple hierarchical dataframe from [](resources/example.csv).

> **Note:**
> Note that this is not a regular CSV file — it contains a column with embedded JSON values.
>
>
>
> To read such files correctly, both the [dataframe-csv](modules.html#dataframe-csv) and [dataframe-json](modules.html#dataframe-json) modules must be included.

This dataframe consists of two columns:

* `name`, which is a `String` column

* `info`, which is a [column group](datacolumn.html#columngroup) containing two nested [value columns](datacolumn.html#valuecolumn): * `age` of type `Int` * `height` of type `Double`

| name | info |
| --- | --- |
|  | age | height |
| Alice | 23 | 175.5 |
| Bob | 27 | 160.2 |

Kotlin Notebook:

Read the [DataFrame](dataframe.html) from the CSV file:

```KOTLIN
val df = DataFrame.readCsv("example.csv")
```

After cell execution data schema and extensions for this `DataFrame` will be generated so you can use extensions for accessing columns, using it in operations inside the [Column Selector DSL](columnselectors.html) and [DataRow API](datarow.html):

```KOTLIN
// Get nested column
df.info.age
// Sort by multiple columns
df.sortBy { name and info.height }
// Filter rows using a row condition. 
// These extensions express the exact value in the row 
// with the corresponding type:
df.filter { name.startsWith("A") && info.age >= 16 }
```

If you change the dataframe's schema by changing any column [name](rename.html), or [type](convert.html) or [add](add.html) a new one, you need to run a cell with a new [DataFrame](dataframe.html) declaration first. For example, rename the `name` column into "firstName":

```KOTLIN
val dfRenamed = df.rename { name }.into("firstName")
```

After running the cell with the code above, you can use `firstName` extensions in the following cells:

```KOTLIN
dfRenamed.firstName
dfRenamed.rename { firstName }.into("name")
dfRenamed.filter { firstName == "Nikita" }
```

> **Note:**
> If you use any operation that changes the dataframe's schema, you can use the [String API](stringapi.html) for further operations on the dataframe.
>
>
>
>
> ```KOTLIN
> df.rename { name }.into("firstName")
> .update("firstName").with { (it as String) ... }
> ```
>
>
>
> However, we'd recommend assigning the dataframe with a new schema to a variable, running the cell, and continuing to work with it in the next cell.

See the [Quickstart Guide](quickstart.html) in Kotlin Notebook with basic Extension Properties API examples.

Compiler Plugin:

For now, if you read [DataFrame](dataframe.html) from a file or URL, you need to define its schema manually. You can do it quickly with [generate..() methods](dataschemagenerationmethods.html).

Define schemas:

```KOTLIN
// Data schema of the "info" column group
@DataSchema
interface Info {
    val age: Int
    val height: Float
}

// Data schema of the entire DataFrame
@DataSchema
interface Person {
    val info: Info
    val name: String
}
```

Read the [DataFrame](dataframe.html) from the CSV file and specify the schema with [.convertTo()](convertto.html) or [cast()](cast.html):

```KOTLIN
val df = DataFrame.readCsv("example.csv").cast<Person>()
```

Extensions for this `DataFrame` will be generated automatically by the plugin, so you can use extensions for accessing columns, using it in operations inside the [Column Selector DSL](columnselectors.html) and [DataRow API](datarow.html).

```KOTLIN
// Get nested column
df.info.age
// Sort by multiple columns
df.sortBy { name and info.height }
// Filter rows using a row condition. 
// These extensions express the exact value in the row 
// with the corresponding type:
df.filter { name.startsWith("A") && info.age >= 16 }
```

Moreover, new extensions will be generated on-the-fly after each schema change: by changing any column [name](rename.html), or [type](convert.html) or [add](add.html) a new one. For example, rename the `name` column into "firstName" and then we can use `firstName` extensions in the following operations:

```KOTLIN
// Rename "name" column into "firstName"
df.rename { name }.into("firstName")
    // Can use `firstName` extension in the row condition 
    // right after renaming
    .filter { firstName == "Nikita" }
```

> **Warning:**
> For some operations, in which resulting columns cannot be inferred by the Compiler Plugin (for example, [pivot](pivot.html)), extension properties can't be inferred automatically either. For such operations, you can [cast](cast.html) to a new data schema or use the String API.

See the [Compiler Plugin Example](https://github.com/Kotlin/dataframe/blob/master/examples/projects/kotlin-dataframe-plugin-gradle-example) IDEA project with basic Extension Properties API examples.

## Properties name generation

By default, each extension property is generated with a name equal to the original column name.

```KOTLIN
val df = dataFrameOf("size_in_inches" to listOf(..))
df.size_in_inches
```

If the original column name cannot be used as a property name (for example, if it contains spaces or has a name equal to a keyword in Kotlin), it will be enclosed in backticks.

```KOTLIN
val df = dataFrameOf("size in inches" to listOf(..))
df.`size in inches`
```

However, sometimes the original column name contains special symbols and can't be used as a property name in backticks. In such cases, special symbols in the auto-generated property name will be replaced.

```KOTLIN
val df = dataFrameOf("size\nin:inches" to listOf(..))
df.`size in - inches`
```

> **Tip:**
> In such cases, use [rename](rename.html) to update column names, or [renameToCamelCase](rename.html#renametocamelcase) to convert all column names in a `DataFrame` to `camelCase`, which is the idiomatic and widely preferred naming style in Kotlin.

If you don't want to change the actual column name, but you need a convenient accessor for this column, you can use the `@ColumnName` annotation in a manually declared [data schema](schemas.html). It allows you to use a property name different from the original column name without changing the column's actual name:

```KOTLIN
@DataSchema
interface Info {
    @ColumnName("size\nin:inches")
    val sizeInInches: Double
}
```

```KOTLIN
val df = dataFrameOf("size\nin:inches" to listOf(..)).cast<Info>()
df.sizeInInches
```

## Custom extension properties

Sometimes it is useful to define your own extension properties based on a [data schema](schema.html).

For example, consider a simple dataframe with two columns and the following `BranchData` schema:

```KOTLIN
@DataSchema
interface BranchData {
    val expenses: Long
    val revenue: Long
}
```

```KOTLIN
// Read DataFrame and cast its type parameter to BranchData
val df = DataFrame.readCsv("branchData.csv").cast<BranchData>()
```

You can define an extension property for `DataRow<BranchData>` to create a convenient shortcut:

```KOTLIN
// Use generated extension properties to create a new one
val DataRow<BranchData>.profit get() = revenue - expenses
```

You can then use it, for example, in [row expressions](datarow.html#row-expressions):

```KOTLIN
val dfProfitable = df.filter { it.profit > 0 }
```

Note that if you change the actual schema of a dataframe (by performing operations that modify its structure), this extension property can no longer be used, because it is tied to the specific schema.

```KOTLIN
df.add("name") { "branchName" }
    // unresolved because of `add`
    .filter { it.profit > 0 }
```

However, you can work around this by casting back to the original schema:

```KOTLIN
df.add("name") { "branchName" }
    .filter { it.cast<BranchData>().profit > 0 }
```

## Troubleshooting

Sometimes you can get an exception with a message containing

```PLAINTEXT
..exception in generated DataFrame extension property..
```

This may be caused by incompatible schema usage or incorrectly defined column types.

See [Data Schemas and Extension Properties Troubleshooting](data-schemas-and-extension-properties-troubleshooting.html) for more information.

