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
)
)
}