@@ -92,20 +92,31 @@ internal fun <T : Comparable<T>> Sequence<Any>.quantileOrNull(
92
92
}
93
93
94
94
/* *
95
- * p -quantile: the k'th q-quantile, where p = k/q.
95
+ * Returns the index `i` of the [p] -quantile: the k'th q-quantile, where p = k/q.
96
96
*
97
+ * The returned index `i` is either exactly or approaching the index of the quantile in the sequence [this]
98
+ * (when it's sorted and NaN's removed).
99
+ * Returns -1.0 if the sequence [this] is empty.
100
+ * Returns [Double.NaN] if `!`[skipNaN] and a NaN is encountered.
97
101
*/
98
- internal fun <T : Comparable <T & Any > ? , Index : Number > Sequence<Any?>.indexOfQuantile (
102
+ internal fun <T : Comparable <T > > Sequence<Any?>.quantileIndexEstimation (
99
103
p : Double ,
100
104
type : KType ,
101
105
skipNaN : Boolean ,
102
- method : QuantileEstimationMethod <T & Any , Index >,
106
+ method : QuantileEstimationMethod <T , * >,
103
107
name : String = "quantile",
104
- ): Index {
108
+ ): Double {
105
109
val nonNullType = type.withNullability(false )
110
+
106
111
when {
107
112
p !in 0.0 .. 1.0 -> error(" Quantile must be in range [0, 1]" )
108
113
114
+ type.isMarkedNullable ->
115
+ error(" Encountered nullable type ${renderType(type)} in $name function. This should not occur." )
116
+
117
+ // this means the sequence is empty
118
+ type == nothingType -> return - 1.0
119
+
109
120
! nonNullType.isIntraComparable() ->
110
121
error(
111
122
" Unable to compute the $name for ${
@@ -120,37 +131,20 @@ internal fun <T : Comparable<T & Any>?, Index : Number> Sequence<Any?>.indexOfQu
120
131
)
121
132
}
122
133
123
- @Suppress(" UNCHECKED_CAST" )
124
- fun Number.toIndex (): Index =
125
- when (method) {
126
- is QuantileEstimationMethod .Selecting -> this .toInt()
127
- is QuantileEstimationMethod .Interpolating -> this .toDouble()
128
- } as Index
129
-
130
134
// propagate NaN to return if they are not to be skipped
131
135
if (nonNullType.canBeNaN && ! skipNaN) {
132
- for ((i, it) in this .withIndex()) {
133
- if (it.isNaN) return i.toIndex()
134
- }
135
- }
136
-
137
- val indexedSequence = this .mapIndexedNotNull { i, it ->
138
- if (it == null ) {
139
- null
140
- } else {
141
- IndexedComparable (i, it as Comparable <Any >)
142
- }
136
+ if (any { it.isNaN }) return Double .NaN
143
137
}
144
138
val list = when {
145
- nonNullType.canBeNaN -> indexedSequence .filterNot { it.value .isNaN }
146
- else -> indexedSequence
139
+ nonNullType.canBeNaN -> this .filterNot { it.isNaN }
140
+ else -> this
147
141
}.toList()
148
142
149
143
val size = list.size
150
- if (size == 0 ) return ( - 1 ).toIndex()
151
- if (size == 1 ) return 0 .toIndex()
144
+ if (size == 0 ) return - 1.0
145
+ if (size == 1 ) return 0.0
152
146
153
- return method.indexOfQuantile(p, size)
147
+ return method.indexOfQuantile(p, size).toDouble()
154
148
}
155
149
156
150
/* *
@@ -183,54 +177,56 @@ internal sealed interface QuantileEstimationMethod<Value : Comparable<Value>, In
183
177
184
178
/* * Inverse of the empirical distribution function. */
185
179
data object R1 : Selecting {
186
- override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Int = ceil(p * count).toInt()
180
+ override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Int =
181
+ ceil(p * count).toInt()
182
+ .coerceIn(1 .. count)
187
183
188
184
@Suppress(" UNCHECKED_CAST" )
189
185
override fun quantile (p : Double , values : List <Comparable <Any >>): Comparable <Any > {
190
186
val h = indexOfQuantile(p, values.size).toInt()
191
- return values.quickSelect(h.coerceIn( 0 .. < values.size) )
187
+ return values.quickSelect(h)
192
188
}
193
189
}
194
190
195
191
/* * The observation closest to `count * p` */
196
192
data object R3 : Selecting {
197
193
// following apache commons + paper instead of wikipedia
198
- override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Int = round(count * p).toInt()
194
+ override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Int =
195
+ round(count * p).toInt()
196
+ .coerceIn(1 .. count)
199
197
200
198
@Suppress(" UNCHECKED_CAST" )
201
199
override fun quantile (p : Double , values : List <Comparable <Any >>): Comparable <Any > {
202
200
val h = indexOfQuantile(p, values.size).toInt()
203
- return values.quickSelect(h.coerceIn( 0 .. < values.size) )
201
+ return values.quickSelect(h)
204
202
}
205
203
}
206
204
207
- // overload to get the right comparable type
208
- @JvmName(" quantileTyped" )
209
- @Suppress(" EXTENSION_SHADOWED_BY_MEMBER" , " UNCHECKED_CAST" , " INAPPLICABLE_JVM_NAME" )
210
- fun <T : Comparable <T >> quantile (p : Double , values : List <T >): T =
211
- quantile(p, values as List <Comparable <Any >>) as T
212
205
}
213
206
214
207
// TODO add R2, R4, R5, R6, R9
215
208
sealed interface Interpolating : QuantileEstimationMethod <Double , Double > {
216
209
217
210
/* * Linear interpolation of the modes for the order statistics for the uniform distribution on [0, 1]. */
218
211
data object R7 : Interpolating , PieceWiseLinear {
219
- override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Double = (count - 1.0 ) * p + 1.0
212
+ override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Double =
213
+ ((count - 1.0 ) * p + 1.0 )
214
+ .coerceIn(1.0 .. count.toDouble())
220
215
}
221
216
222
217
/* * Linear interpolation of the approximate medians for order statistics. */
223
218
data object R8 : Interpolating , PieceWiseLinear {
224
- override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Double = (count + 1.0 / 3.0 ) * p + 1.0 / 3.0
219
+ override fun oneBasedIndexOfQuantile (p : Double , count : Int ): Double =
220
+ ((count + 1.0 / 3.0 ) * p + 1.0 / 3.0 )
221
+ .coerceIn(1.0 .. count.toDouble())
225
222
}
226
223
227
224
private interface PieceWiseLinear : Interpolating {
228
225
override fun quantile (p : Double , values : List <Double >): Double {
229
226
val h = oneBasedIndexOfQuantile(p, values.size)
230
- return values.quickSelect((floor(h).toInt() - 1 ).coerceIn(0 .. < values.size)) +
231
- (h - floor(h)) * (
232
- values.quickSelect((ceil(h).toInt() - 1 ).coerceIn(0 .. < values.size)) -
233
- values.quickSelect((floor(h).toInt() - 1 ).coerceIn(0 .. < values.size))
227
+ return values.quickSelect(floor(h).toInt() - 1 ) + (h - floor(h)) * (
228
+ values.quickSelect(ceil(h).toInt() - 1 ) -
229
+ values.quickSelect(floor(h).toInt() - 1 )
234
230
)
235
231
}
236
232
}
@@ -246,6 +242,15 @@ internal sealed interface QuantileEstimationMethod<Value : Comparable<Value>, In
246
242
}
247
243
}
248
244
245
+ // overload to get the right comparable type
246
+ @Suppress(" UNCHECKED_CAST" )
247
+ internal fun <T : Comparable <T >> QuantileEstimationMethod.Selecting.quantile (p : Double , values : List <T >): T =
248
+ quantile(p, values as List <Comparable <Any >>) as T
249
+
250
+ @Suppress(" UNCHECKED_CAST" )
251
+ internal fun <T : Comparable <T >> QuantileEstimationMethod.Selecting.cast (): QuantileEstimationMethod <T , Int > =
252
+ this as QuantileEstimationMethod <T , Int >
253
+
249
254
// corrects oneBasedIndexOfQuantile to zero-based index
250
255
@Suppress(" UNCHECKED_CAST" )
251
256
internal fun <IndexType : Number > QuantileEstimationMethod <* , IndexType >.indexOfQuantile (
0 commit comments