@@ -17,16 +17,24 @@ import org.jetbrains.kotlinx.dataframe.api.getColumns
17
17
import org.jetbrains.kotlinx.dataframe.api.map
18
18
import org.jetbrains.kotlinx.dataframe.api.named
19
19
import org.jetbrains.kotlinx.dataframe.api.toColumn
20
+ import org.jetbrains.kotlinx.dataframe.api.toColumnGroup
20
21
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
22
+ import org.jetbrains.kotlinx.dataframe.columns.BaseColumn
23
+ import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
21
24
import org.jetbrains.kotlinx.multik.api.mk
22
25
import org.jetbrains.kotlinx.multik.api.ndarray
23
26
import org.jetbrains.kotlinx.multik.ndarray.complex.Complex
24
27
import org.jetbrains.kotlinx.multik.ndarray.data.D1Array
25
28
import 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
26
32
import org.jetbrains.kotlinx.multik.ndarray.data.get
27
33
import org.jetbrains.kotlinx.multik.ndarray.operations.toList
34
+ import org.jetbrains.kotlinx.multik.ndarray.operations.toListD2
28
35
import kotlin.experimental.ExperimentalTypeInference
29
36
import kotlin.reflect.KClass
37
+ import kotlin.reflect.KType
30
38
import kotlin.reflect.full.isSubtypeOf
31
39
import kotlin.reflect.typeOf
32
40
@@ -41,6 +49,7 @@ inline fun <reified N> D1Array<N>.convertToColumn(name: String = ""): DataColumn
41
49
*
42
50
* @return a DataFrame where each element of the source array is represented as a row in a column named "value" under the schema [ValueProperty].
43
51
*/
52
+ @JvmName(" convert1dArrayToDataFrame" )
44
53
inline fun <reified N > D1Array<N>.convertToDataFrame (): DataFrame <ValueProperty <N >> =
45
54
dataFrameOf(ValueProperty <* >::value.name to column(toList()))
46
55
.cast()
@@ -80,11 +89,12 @@ inline fun <T, reified N : Complex> DataFrame<T>.convertToMultik(crossinline col
80
89
*
81
90
* The conversion enforces that `multikArray[x][y] == dataframe[x][y]`
82
91
*/
92
+ @JvmName(" convert2dArrayToDataFrame" )
83
93
inline 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
86
96
.toList()
87
- .toColumn(columnNameGenerator(col ))
97
+ .toColumn(columnNameGenerator(i ))
88
98
}.toDataFrame()
89
99
90
100
/* *
@@ -179,3 +189,117 @@ inline fun <reified N : Complex> List<DataColumn<N>>.convertToMultik(): D2Array<
179
189
mk.ndarray(toDataFrame().map { it.values() as List <N > })
180
190
181
191
// 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
+ ).let { dataFrameOf((it as ColumnGroup <* >).columns()) }
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