fun buildStatData()

in plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/data/DataProcessing.kt [49:179]


    fun buildStatData(
        statInput: StatInput,
        stat: Stat,
        groupingContext: GroupingContext,
        facetVariables: List<Variable>,
        varsWithoutBinding: List<String>,
        orderOptions: List<OrderOptionUtil.OrderOption>,
        aggregateOperation: ((List<Double?>) -> Double?)?,
        messageConsumer: Consumer<String>
    ): DataAndGroupMapper {
        check(stat != Stats.IDENTITY)

        val groups = groupingContext.groupMapper

        val resultSeries: Map<Variable, List<Any?>>
        val groupSizeListAfterStat: List<Int>

        // if only one group no need to modify
        if (groups === SINGLE_GROUP) {
            val statData = applyStat(
                statInput.data,
                stat,
                statInput.bindings,
                statInput.transformByAes,
                facetVariables,
                statInput.statCtx,
                varsWithoutBinding,
                messageConsumer
            )
            groupSizeListAfterStat = listOf(statData.rowCount())
            resultSeries = statData.variables().associateWith { variable -> statData[variable] }
        } else {
            val groupMerger = GroupMerger(aggregateOperation)
            var lastStatGroupEnd = -1
            for ((groupId, d) in splitByGroup(statInput.data, groups)) {
                var statData = applyStat(
                    d,
                    stat,
                    statInput.bindings,
                    statInput.transformByAes,
                    facetVariables,
                    statInput.statCtx,
                    varsWithoutBinding,
                    messageConsumer
                )

                check(!statData.isEmpty)

                val curGroupSizeAfterStat = statData.rowCount()

                if (statData.has(Stats.GROUP)) {
                    // update 'stat group' to avoid collisions as stat is applied independently to each original data group
                    val range = statData.range(Stats.GROUP)
                    if (range != null) {
                        val start = lastStatGroupEnd + 1
                        val offset = start - range.lowerEnd.toInt()
                        lastStatGroupEnd = range.upperEnd.toInt() + offset
                        if (offset != 0) {
                            val newG = ArrayList<Double>()
                            for (g in statData.getNumeric(Stats.GROUP)) {
                                newG.add(g!! + offset)
                            }
                            statData = statData.builder().putNumeric(Stats.GROUP, newG).build()
                        }
                    }
                } else {
                    // Just recreate grouping vars.
                    if (groupingContext.groupingVariables.isNotEmpty()) {
                        val size = statData.rowCount()
                        val builder = statData.builder()
                        groupingContext.groupingVariables.forEach { groupingVar ->
                            val v = d[groupingVar][0]
                            builder.put(groupingVar, List(size) { v })
                        }
                        statData = builder.build()
                    }
                }

                // Add data "after stat" (i.e. group data) to the group merger.
                if (groupMerger.isEmpty) {
                    val orderOptionsMinusX = orderOptions
                        .filter { orderOption ->
                            // no need to reorder groups by X
                            statInput.bindings.find { it.variable.name == orderOption.variableName && it.aes == Aes.X } == null
                        }
                    // Init order specs in Group merger.
                    groupMerger.orderSpecs = OrderOptionUtil.createOrderSpecs(
                        orderOptionsMinusX,
                        statData.variables(),
                        statInput.bindings,
                        aggregateOperation
                    )
                }

                groupMerger.addGroup(groupId, statData, curGroupSizeAfterStat)
            }
            // Get merged series
            resultSeries = groupMerger.getResultSeries()
            groupSizeListAfterStat = groupMerger.getGroupSizes()
        }

        val dataAfterStat = Builder().run {
            // put results
            for (variable in resultSeries.keys) {
                put(variable, resultSeries[variable]!!)
            }

            // Set ordering specifications to the tile data "after stat".
            // Ordering is required for possible "pick sampling" down the stream.
            val orderSpecs = OrderOptionUtil.createOrderSpecs(
                orderOptions,
                resultSeries.keys,
                statInput.bindings,
                aggregateOperation
            )
            addOrderSpecs(orderSpecs)

            // build DataFrame
            build()
        }

        val normalizedData = stat.normalize(dataAfterStat)

        return DataAndGroupMapper(
            data = normalizedData,
            groupMapper = createGroupMapperByGroupSizes(
                data = normalizedData,
                groupSizeList = groupSizeListAfterStat
            )
        )
    }