@@ -17,16 +17,24 @@ import org.jetbrains.kotlinx.dataframe.api.getColumns
1717import org.jetbrains.kotlinx.dataframe.api.map
1818import org.jetbrains.kotlinx.dataframe.api.named
1919import org.jetbrains.kotlinx.dataframe.api.toColumn
20+ import org.jetbrains.kotlinx.dataframe.api.toColumnGroup
2021import org.jetbrains.kotlinx.dataframe.api.toDataFrame
22+ import org.jetbrains.kotlinx.dataframe.columns.BaseColumn
23+ import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
2124import org.jetbrains.kotlinx.multik.api.mk
2225import org.jetbrains.kotlinx.multik.api.ndarray
2326import org.jetbrains.kotlinx.multik.ndarray.complex.Complex
2427import org.jetbrains.kotlinx.multik.ndarray.data.D1Array
2528import org.jetbrains.kotlinx.multik.ndarray.data.D2Array
29+ import org.jetbrains.kotlinx.multik.ndarray.data.D3Array
30+ import org.jetbrains.kotlinx.multik.ndarray.data.MultiArray
31+ import org.jetbrains.kotlinx.multik.ndarray.data.NDArray
2632import org.jetbrains.kotlinx.multik.ndarray.data.get
2733import org.jetbrains.kotlinx.multik.ndarray.operations.toList
34+ import org.jetbrains.kotlinx.multik.ndarray.operations.toListD2
2835import kotlin.experimental.ExperimentalTypeInference
2936import kotlin.reflect.KClass
37+ import kotlin.reflect.KType
3038import kotlin.reflect.full.isSubtypeOf
3139import kotlin.reflect.typeOf
3240
@@ -41,6 +49,7 @@ inline fun <reified N> D1Array<N>.convertToColumn(name: String = ""): DataColumn
4149 *
4250 * @return a DataFrame where each element of the source array is represented as a row in a column named "value" under the schema [ValueProperty].
4351 */
52+ @JvmName(" convert1dArrayToDataFrame" )
4453inline fun <reified N > D1Array<N>.convertToDataFrame (): DataFrame <ValueProperty <N >> =
4554 dataFrameOf(ValueProperty <* >::value.name to column(toList()))
4655 .cast()
@@ -80,11 +89,12 @@ inline fun <T, reified N : Complex> DataFrame<T>.convertToMultik(crossinline col
8089 *
8190 * The conversion enforces that `multikArray[x][y] == dataframe[x][y]`
8291 */
92+ @JvmName(" convert2dArrayToDataFrame" )
8393inline fun <reified N > D2Array<N>.convertToDataFrame (columnNameGenerator : (Int ) -> String = { "col$it" }): AnyFrame =
84- ( 0 .. < shape[1 ]).map { col ->
85- this [0 .. < shape[0 ], col]
94+ List ( shape[1 ]) { i ->
95+ this [0 .. < shape[0 ], i] // get all cells of column i
8696 .toList()
87- .toColumn(columnNameGenerator(col ))
97+ .toColumn(columnNameGenerator(i ))
8898 }.toDataFrame()
8999
90100/* *
@@ -179,3 +189,117 @@ inline fun <reified N : Complex> List<DataColumn<N>>.convertToMultik(): D2Array<
179189 mk.ndarray(toDataFrame().map { it.values() as List <N > })
180190
181191// endregion
192+
193+ // region higher dimensions
194+
195+ /* *
196+ * Converts a three-dimensional array ([D3Array]) to a DataFrame.
197+ * It will contain `shape[0]` rows and `shape[1]` columns containing lists of size `shape[2]`.
198+ *
199+ * Column names can be specified using the [columnNameGenerator] lambda.
200+ *
201+ * The conversion enforces that `multikArray[x][y][z] == dataframe[x][y][z]`
202+ */
203+ inline fun <reified N > D3Array<N>.convertToDataFrameWithLists (
204+ columnNameGenerator : (Int ) -> String = { "col$it" },
205+ ): AnyFrame =
206+ List (shape[1 ]) { y ->
207+ this [0 .. < shape[0 ], y, 0 .. < shape[2 ]] // get all cells of column y, each is a 2d array of size shape[0] x shape[2]
208+ .toListD2() // get a shape[0]-sized list/column filled with lists of size shape[2]
209+ .toColumn(columnNameGenerator(y))
210+ }.toDataFrame()
211+
212+ /* *
213+ * Converts a three-dimensional array ([D3Array]) to a DataFrame.
214+ * It will contain `shape[0]` rows and `shape[1]` column groups containing `shape[2]` columns each.
215+ *
216+ * Column names can be specified using the [columnNameGenerator] lambda.
217+ *
218+ * The conversion enforces that `multikArray[x][y][z] == dataframe[x][y][z]`
219+ */
220+ @JvmName(" convert3dArrayToDataFrame" )
221+ inline fun <reified N > D3Array<N>.convertToDataFrame (columnNameGenerator : (Int ) -> String = { "col$it" }): AnyFrame =
222+ List (shape[1 ]) { y ->
223+ this [0 .. < shape[0 ], y, 0 .. < shape[2 ]] // get all cells of column i, each is a 2d array of size shape[0] x shape[2]
224+ .transpose(1 , 0 ) // flip, so we get shape[2] x shape[0]
225+ .toListD2() // get a shape[2]-sized list filled with lists of size shape[0]
226+ .mapIndexed { z, list ->
227+ list.toColumn(columnNameGenerator(z))
228+ } // we get shape[2] columns inside each column group
229+ .toColumnGroup(columnNameGenerator(y))
230+ }.toDataFrame()
231+
232+ /* *
233+ * Exploratory recursive function to convert a [MultiArray] of any number of dimensions
234+ * to a `List<List<...>>` of the same number of dimensions.
235+ */
236+ fun <T > MultiArray <T , * >.toListDn (): List <* > {
237+ // Recursive helper function to handle traversal across dimensions
238+ fun toListRecursive (indices : IntArray ): List <* > {
239+ // If we are at the last dimension (1D case)
240+ if (indices.size == shape.lastIndex) {
241+ return List (shape[indices.size]) { i ->
242+ this [intArrayOf(* indices, i)] // Collect values for this dimension
243+ }
244+ }
245+
246+ // For higher dimensions, recursively process smaller dimensions
247+ return List (shape[indices.size]) { i ->
248+ toListRecursive(indices + i) // Add `i` to the current index array
249+ }
250+ }
251+ return toListRecursive(intArrayOf())
252+ }
253+
254+ /* *
255+ * Converts a multidimensional array ([NDArray]) to a DataFrame.
256+ * Inspired by [toListDn].
257+ *
258+ * For a single-dimensional array, it will call [D1Array.convertToDataFrame].
259+ *
260+ * Column names can be specified using the [columnNameGenerator] lambda.
261+ *
262+ * The conversion enforces that `multikArray[a][b][c][d]... == dataframe[a][b][c][d]...`
263+ */
264+ inline fun <reified N > NDArray <N , * >.convertToDataFrameNestedGroups (
265+ noinline columnNameGenerator : (Int ) -> String = { "col$it" },
266+ ): AnyFrame {
267+ if (shape.size == 1 ) return (this as D1Array <N >).convertToDataFrame()
268+
269+ // push the first dimension to the end, because this represents the rows in DataFrame,
270+ // and they are accessed by []'s first
271+ return transpose(* (1 .. < dim.d).toList().toIntArray(), 0 )
272+ .convertToDataFrameNestedGroupsRecursive(
273+ indices = intArrayOf(),
274+ type = typeOf<N >(),
275+ columnNameGenerator = columnNameGenerator,
276+ ) as ColumnGroup <* >
277+ }
278+
279+ // Recursive helper function to handle traversal across dimensions
280+ @PublishedApi
281+ internal fun NDArray <* , * >.convertToDataFrameNestedGroupsRecursive (
282+ indices : IntArray ,
283+ type : KType ,
284+ columnNameGenerator : (Int ) -> String = { "col$it" },
285+ ): BaseColumn <* > {
286+ // If we are at the last dimension (1D case)
287+ if (indices.size == shape.lastIndex) {
288+ return List (shape[indices.size]) { i ->
289+ this [intArrayOf(* indices, i)] // Collect values for this dimension
290+ }.let {
291+ DataColumn .createByType(name = " " , values = it, type = type)
292+ }
293+ }
294+
295+ // For higher dimensions, recursively process smaller dimensions
296+ return List (shape[indices.size]) { i ->
297+ convertToDataFrameNestedGroupsRecursive(
298+ indices = indices + i, // Add `i` to the current index array
299+ type = type,
300+ columnNameGenerator = columnNameGenerator,
301+ ).rename(columnNameGenerator(i))
302+ }.toColumnGroup(" " )
303+ }
304+
305+ // endregion
0 commit comments