Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECDF Stat #832

Merged
merged 17 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add pads to the ECDFStat.
  • Loading branch information
ASmirnov-HORIS committed Aug 2, 2023
commit 5ff48c20857b1ceca71402e726329b6ddc13a884
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@

package org.jetbrains.letsPlot.core.plot.base.geom

import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.commons.interval.DoubleSpan
import org.jetbrains.letsPlot.core.commons.data.SeriesUtil
import org.jetbrains.letsPlot.core.plot.base.*
import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper
import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil
import org.jetbrains.letsPlot.core.plot.base.geom.util.LinesHelper
import org.jetbrains.letsPlot.core.plot.base.geom.util.TargetCollectorHelper
import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgLineElement

class StepGeom : LineGeom(), WithHeight {
class StepGeom : LineGeom() {
private var myDirection = DEF_DIRECTION
var padded = DEF_PADDED

Expand All @@ -30,86 +29,29 @@ class StepGeom : LineGeom(), WithHeight {
coord: CoordinateSystem,
ctx: GeomContext
) {
val dataPoints = dataPoints(aesthetics)
val dataPoints = GeomUtil.ordered_X(aesthetics.dataPoints())
val linesHelper = LinesHelper(pos, coord, ctx)

val pathDataList = linesHelper.createPathDataByGroup(dataPoints, GeomUtil.TO_LOCATION_X_Y)
val pathDataList = linesHelper.createPathDataByGroup(dataPoints, toLocationFor(overallAesBounds(ctx)))
val linePaths = linesHelper.createSteps(pathDataList, myDirection)

root.appendNodes(linePaths)

if (padded) {
GeomUtil.withDefined(dataPoints, Aes.X, Aes.Y).groupBy(DataPointAesthetics::group).forEach { (_, groupPoints) ->
for (line in getPads(groupPoints, pos, coord, ctx)) {
root.add(line)
}
}
}

val targetCollectorHelper = TargetCollectorHelper(GeomKind.STEP, ctx)
targetCollectorHelper.addPaths(pathDataList)
}

private fun getPads(
dataPoints: Iterable<DataPointAesthetics>,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
): List<SvgLineElement> {
val viewPort = overallAesBounds(ctx)
val helper = GeomHelper(pos, coord, ctx).createSvgElementHelper()
helper.setStrokeAlphaEnabled(true)

val definedPoints = GeomUtil.withDefined(dataPoints, Aes.X, Aes.Y)
if (!definedPoints.any()) {
return emptyList()
}

val firstPoint = definedPoints.first()
val firstX = firstPoint.x()!!
val firstY = firstPoint.y()!!
val lastPoint = definedPoints.last()
val lastX = lastPoint.x()!!
val lastY = lastPoint.y()!!

if (firstY < 0 || lastY > 1) {
return emptyList()
}

return listOfNotNull(
helper.createLine(
DoubleVector(viewPort.left, 0.0),
DoubleVector(firstX, 0.0),
firstPoint
),
helper.createLine(
DoubleVector(firstX, 0.0),
DoubleVector(firstX, firstY),
firstPoint
),
helper.createLine(
DoubleVector(lastX, lastY),
DoubleVector(lastX, 1.0),
lastPoint
),
helper.createLine(
DoubleVector(lastX, 1.0),
DoubleVector(viewPort.right, 1.0),
lastPoint
)
)
}

override fun heightSpan(
p: DataPointAesthetics,
coordAes: Aes<Double>,
resolution: Double,
isDiscrete: Boolean
): DoubleSpan? {
return when {
padded -> DoubleSpan(0.0, 1.0)
p.x()?.isFinite() == true && p.y()?.isFinite() == true -> DoubleSpan(p.y()!!, p.y()!!)
else -> DoubleSpan(-0.5, 0.5)
private fun toLocationFor(viewPort: DoubleRectangle): (DataPointAesthetics) -> DoubleVector? {
return { p ->
val x = p.x()
val y = p.y()
when {
SeriesUtil.isFinite(x) && SeriesUtil.isFinite(y) -> DoubleVector(x!!, y!!)
!SeriesUtil.isFinite(y) -> null
padded && x == Double.NEGATIVE_INFINITY -> DoubleVector(viewPort.left, y!!)
padded && x == Double.POSITIVE_INFINITY -> DoubleVector(viewPort.right, y!!)
else -> null
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import org.jetbrains.letsPlot.core.plot.base.StatContext
import org.jetbrains.letsPlot.core.plot.base.data.TransformVar

class ECDFStat(
private val n: Int?
private val n: Int?,
private val padded: Boolean
) : BaseStat(DEF_MAPPING) {

override fun consumes(): List<Aes<*>> {
Expand Down Expand Up @@ -49,10 +50,20 @@ class ECDFStat(
linspace(xValues.min(), xValues.max(), n)
}
val statY = statX.map { ecdf(it) }
val padX = if (padded) {
listOf(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)
} else {
emptyList()
}
val padY = if (padded) {
listOf(0.0, 1.0)
} else {
emptyList()
}

return mapOf(
Stats.X to statX,
Stats.Y to statY,
Stats.X to statX + padX,
Stats.Y to statY + padY,
)
}

Expand All @@ -64,6 +75,8 @@ class ECDFStat(
}

companion object {
const val DEF_PADDED = true

private val DEF_MAPPING: Map<Aes<*>, DataFrame.Variable> = mapOf(
Aes.X to Stats.X,
Aes.Y to Stats.Y
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlin.test.Test
class ECDFStatTest : BaseStatTest() {
@Test
fun emptyDataFrame() {
testEmptyDataFrame(ECDFStat(null))
testEmptyDataFrame(ECDFStat(null, false))
}

@Test
Expand All @@ -20,7 +20,7 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(x)
))
val stat = ECDFStat(null)
val stat = ECDFStat(null, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(x))
Expand All @@ -32,7 +32,7 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(null, null, null)
))
val stat = ECDFStat(null)
val stat = ECDFStat(null, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, emptyList())
Expand All @@ -44,7 +44,7 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(-1.0, null, null, 1.0)
))
val stat = ECDFStat(null)
val stat = ECDFStat(null, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(-1.0, 1.0))
Expand All @@ -56,7 +56,7 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(-2.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 2.0)
))
val stat = ECDFStat(null)
val stat = ECDFStat(null, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(-2.0, -1.0, 0.0, 1.0, 2.0))
Expand All @@ -68,7 +68,7 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(2.0, -1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 1.0, -2.0)
))
val stat = ECDFStat(null)
val stat = ECDFStat(null, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(2.0, -1.0, 0.0, 1.0, -2.0))
Expand All @@ -80,10 +80,22 @@ class ECDFStatTest : BaseStatTest() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(-2.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 2.0)
))
val stat = ECDFStat(3)
val stat = ECDFStat(3, false)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(-2.0, 0.0, 2.0))
checkStatVarValues(statDf, Stats.Y, listOf(0.1, 0.7, 1.0))
}

@Test
fun withPads() {
val df = dataFrame(mapOf(
TransformVar.X to listOf(-2.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 2.0)
))
val stat = ECDFStat(null, true)
val statDf = stat.apply(df, statContext(df))

checkStatVarValues(statDf, Stats.X, listOf(-2.0, -1.0, 0.0, 1.0, 2.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))
checkStatVarValues(statDf, Stats.Y, listOf(0.1, 0.3, 0.7, 0.9, 1.0, 0.0, 1.0))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ object Option {

object ECDF {
const val N = "n"
const val PADDED = "pad"
}

object Summary {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ object StatProto {

StatKind.ECDF -> {
return ECDFStat(
n = options.getInteger(ECDF.N)
n = options.getInteger(ECDF.N),
padded = options.getBoolean(ECDF.PADDED, ECDFStat.DEF_PADDED)
)
}

Expand Down