• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PolyMathOrg / DataFrame / 13409391746

19 Feb 2025 09:30AM UTC coverage: 94.756%. Remained the same
13409391746

push

github

web-flow
Enable Pharo 12 and 13 for the CI

13571 of 14322 relevant lines covered (94.76%)

4.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.29
/src/DataFrame/DataSeries.class.st
1
Class {
2
        #name : 'DataSeries',
3
        #superclass : 'OrderedDictionary',
4
        #instVars : [
5
                'name',
6
                'forcedIsNumerical'
7
        ],
8
        #category : 'DataFrame-Core',
9
        #package : 'DataFrame',
10
        #tag : 'Core'
11
}
12

13
{ #category : 'instance creation' }
14
DataSeries class >> newFrom: aCollection [
5✔
15

5✔
16
        aCollection ifEmpty: [ ^ self new ].
5✔
17

5✔
18
        aCollection species == self ifTrue: [ ^ super newFrom: aCollection associations ].
5✔
19

5✔
20
        "If it's a collection of associations use the superclass implementation"
5✔
21
        ^ super newFrom: (aCollection anyOne isAssociation
5✔
22
                           ifTrue: [ aCollection ]
5✔
23
                           ifFalse: [ aCollection withIndexCollect: [ :each :i | i -> each ] ])
5✔
24
]
5✔
25

26
{ #category : 'instance creation' }
27
DataSeries class >> newFromKeys: keys andValues: values [
5✔
28

5✔
29
        | dict |
5✔
30
        self flag: #pharo12. "This is a copy of the superclass with a speed up. I'll propose this speedup in Pharo 12 so when Pharo 12 will be the minimal suuported version then we can drop this method."
5✔
31
        dict := self new: keys size.
5✔
32
        keys with: values do: [ :k :v | dict at: k put: v ].
5✔
33
        ^ dict
5✔
34
]
5✔
35

36
{ #category : 'instance creation' }
37
DataSeries class >> withKeys: keys values: values [
5✔
38
        ^ self newFromKeys: keys andValues: values
5✔
39
]
5✔
40

41
{ #category : 'instance creation' }
42
DataSeries class >> withKeys: keys values: values name: aName [
5✔
43
        ^ (self withKeys: keys values: values) name: aName; yourself
5✔
44
]
5✔
45

46
{ #category : 'instance creation' }
47
DataSeries class >> withValues: values [
5✔
48
        | keys |
5✔
49
        keys := (1 to: values size) asArray.
5✔
50
        ^ self withKeys: keys values: values
5✔
51
]
5✔
52

53
{ #category : 'instance creation' }
54
DataSeries class >> withValues: values name: aName [
5✔
55
        | keys |
5✔
56
        keys := (1 to: values size) asArray.
5✔
57
        ^ (self withKeys: keys values: values) name: aName; yourself
5✔
58
]
5✔
59

60
{ #category : 'comparing' }
61
DataSeries >> < arg [
5✔
62
        "Element-wise comparision between two DataSeries.
5✔
63
         Does not consider keys for comparision."
5✔
64

5✔
65
        ^ arg adaptToCollection: self andSend: #<
5✔
66
]
5✔
67

68
{ #category : 'comparing' }
69
DataSeries >> <= arg [
5✔
70
        "Element-wise comparision between two DataSeries.
5✔
71
         Does not consider keys for comparision."
5✔
72

5✔
73
        ^ arg adaptToCollection: self andSend: #<=
5✔
74
]
5✔
75

76
{ #category : 'comparing' }
77
DataSeries >> = anObject [
5✔
78
        (super = anObject)
5✔
79
                ifFalse: [ ^ false ].
5✔
80

5✔
81
        ^ anObject name = self name
5✔
82
                "order of keys"
5✔
83
                and: [ anObject keys = self keys ]
5✔
84
]
5✔
85

86
{ #category : 'comparing' }
87
DataSeries >> > arg [
88
        "Element-wise comparision between two DataSeries.
89
         Does not consider keys for comparision."
90

91
        ^ arg adaptToCollection: self andSend: #>
92
]
93

94
{ #category : 'comparing' }
95
DataSeries >> >= arg [
5✔
96
        "Element-wise comparision between two DataSeries.
5✔
97
         Does not consider keys for comparision."
5✔
98

5✔
99
        ^ arg adaptToCollection: self andSend: #>=
5✔
100
]
5✔
101

102
{ #category : 'adapting' }
103
DataSeries >> adaptToCollection: rcvr andSend: selector [
5✔
104
        "If I am involved in arithmetic with another Collection, return a Collection of
5✔
105
        the results of each element combined with the scalar in that expression."
5✔
106

5✔
107
        (rcvr isSequenceable and: [ self isSequenceable ]) ifFalse: [ self error: 'Only sequenceable collections may be combined arithmetically' ].
5✔
108

5✔
109

5✔
110
        ^ rcvr withSeries: self collect: [ :rcvrElement :myElement |
5✔
111
                  (rcvrElement isNil or: [ myElement isNil ])
5✔
112
                          ifTrue: [ nil ]
5✔
113
                          ifFalse: [ rcvrElement perform: selector with: myElement ] ]
5✔
114
]
5✔
115

116
{ #category : 'statistics' }
117
DataSeries >> argmax [
5✔
118
        "Returns the key which corresponds to the maximum value of the dataseries"
5✔
119

5✔
120
        "(#(100 10 20 30) asDataSeries argmax) >>> 1"
5✔
121

5✔
122
        "((DataSeries withKeys: #(A B C) values: #(1 2 40)) argmax) >>> #C"
5✔
123

5✔
124
        ^ self keyAtValue: self max
5✔
125
]
5✔
126

127
{ #category : 'statistics' }
128
DataSeries >> argmin [
5✔
129
        "Returns the key which corresponds to the minimum value of the dataseries"
5✔
130

5✔
131
        "(#(100 10 20 30) asDataSeries argmin) >>> 2"
5✔
132

5✔
133
        "((DataSeries withKeys: #(A B C) values: #(1 2 40)) argmin) >>> #A"
5✔
134

5✔
135
        ^ self keyAtValue: self min
5✔
136
]
5✔
137

138
{ #category : 'converting' }
139
DataSeries >> asDataFrame [
5✔
140
        "Converts a data series to a data frame with 1 column. The values in the column of the data frame are the values of the data series. The row names of this data frame are the keys of the data series. The column name of the data frame is same as the name of the data series"
5✔
141
        
5✔
142
        ^ DataFrame
5✔
143
                withColumns: {  self values }
5✔
144
                rowNames: self keys
5✔
145
                columnNames: { self name }
5✔
146
]
5✔
147

148
{ #category : 'accessing' }
149
DataSeries >> at: aKey transform: aBlock [
5✔
150
        "Evaluate aBlock on the value at aKey and replace that value with the result. Signal an exception if aKey was not found"
5✔
151

5✔
152
        "((DataSeries withKeys: #(A B C) values: #(1 4 3)) at: #C transform: [ :x | x * x ]) >>> (DataSeries withKeys: #(A B C) values: #(1 4 9))."
5✔
153

5✔
154
        self
5✔
155
                at: aKey
5✔
156
                transform: aBlock
5✔
157
                ifAbsent: [ self errorKeyNotFound: aKey ]
5✔
158
]
5✔
159

160
{ #category : 'accessing' }
161
DataSeries >> at: aKey transform: aBlock ifAbsent: exceptionBlock [
5✔
162
        "Evaluate aBlock on the value at aKey and replace that value with the result. Evaluate exceptionBlock if aKey was not found"
5✔
163
        | oldValue |
5✔
164
        oldValue := self at: aKey ifAbsent: [
5✔
165
                exceptionBlock value.
5✔
166
                ^ self ].
5✔
167

5✔
168
        self at: aKey put: (aBlock value: oldValue)
5✔
169
]
5✔
170

171
{ #category : 'accessing' }
172
DataSeries >> atAll: aCollectionOfIndexes [
×
173
        "Returns a data series of only those elements of the receiver whose indices are present in the collection aCollectionOfIndexes"
×
174
        
×
175
        ^ self withIndexSelect: [ :each :index | aCollectionOfIndexes includes: index ]
×
176
]
×
177

178
{ #category : 'accessing' }
179
DataSeries >> atIndex: aNumber [
5✔
180
        "Answer the element of the receiver at index aNumber"
5✔
181

5✔
182
        "(#(1 4 9) asDataSeries atIndex: 2) >>> 4"
5✔
183

5✔
184
        "((DataSeries withKeys: #(A B C) values: #(1 2 40)) atIndex: 3) >>> 40"
5✔
185

5✔
186
        ^ self at: (self keys at: aNumber)
5✔
187
]
5✔
188

189
{ #category : 'accessing' }
190
DataSeries >> atIndex: aNumber put: aValue [
5✔
191
        "Replace the element of the receiver at index aNumber with the value aValue"
5✔
192

5✔
193
        ^ self at: (self keys at: aNumber) put: aValue
5✔
194
]
5✔
195

196
{ #category : 'accessing' }
197
DataSeries >> atIndex: aNumber transform: aBlock [
5✔
198
        "Evaluate aBlock on the value at aNumber and replace that value with the result"
5✔
199

5✔
200
        "        ((DataSeries withKeys: #( A B C ) values: #( 1 4 3 ))
5✔
201
                 atIndex: 3
5✔
202
                 transform: [ :x | x * x ])
5✔
203
        >>> (DataSeries withKeys: #( A B C ) values: #( 1 4 9 ))"
5✔
204

5✔
205
        "(#( 1 3 3 ) asDataSeries atIndex: 2 transform: [ :x | x - 1 ])
5✔
206
        >>> (#( 1 2 3 ) asDataSeries)"
5✔
207

5✔
208
        | key |
5✔
209
        key := self keys at: aNumber.
5✔
210
        self at: key transform: aBlock
5✔
211
]
5✔
212

213
{ #category : 'statistics' }
214
DataSeries >> average [
5✔
215
        "Returns the average without including nils"
5✔
216

5✔
217
        "(#(1 2 nil 3) asDataSeries average) >>> 2"
5✔
218

5✔
219
        ^ self removeNils values average
5✔
220
]
5✔
221

222
{ #category : 'data-types' }
223
DataSeries >> calculateDataType [
5✔
224
        "Returns the data type of the data series"
5✔
225

5✔
226
        "(#(1 2 3) asDataSeries calculateDataType) >>> SmallInteger"
5✔
227

5✔
228
        "(#(1 a 3) asDataSeries calculateDataType) >>> Object"
5✔
229

5✔
230
        "(#(1.1 2.5 3.7) asDataSeries calculateDataType) >>> SmallFloat64"
5✔
231

5✔
232
        "(#(1.1 2.5 3) asDataSeries calculateDataType) >>> Number"
5✔
233

5✔
234
        ^ self values calculateDataType
5✔
235
]
5✔
236

237
{ #category : 'comparing' }
238
DataSeries >> closeTo: anObject [
5✔
239
        ^ self closeTo: anObject precision: self defaultPrecision
5✔
240
]
5✔
241

242
{ #category : 'comparing' }
243
DataSeries >> closeTo: anObject precision: aPrecision [
5✔
244
        self == anObject
5✔
245
                ifTrue: [^ true].
5✔
246

5✔
247
        (self species == anObject species
5✔
248
                and: [self size = anObject size])
5✔
249
                ifFalse: [^ false].
5✔
250

5✔
251
        (anObject name = self name)
5✔
252
                ifFalse: [ ^ false ].
5✔
253

5✔
254
        (anObject keys = self keys)
5✔
255
                ifFalse: [ ^ false ].
5✔
256

5✔
257
        ^ (1 to: self values size)
5✔
258
                detect: [ :i | ((self atIndex: i) closeTo: (anObject atIndex: i) precision: aPrecision) not ]
5✔
259
                ifFound: [ false ]
5✔
260
                ifNone: [ true ]
5✔
261
]
5✔
262

263
{ #category : 'enumerating' }
264
DataSeries >> collect: aBlock [
5✔
265
        "Applies aBlock to every element"
5✔
266

5✔
267
        | result |
5✔
268
        result :=  super collect: aBlock.
5✔
269
        result name: self name.
5✔
270
        ^ result
5✔
271
]
5✔
272

273
{ #category : 'enumerating' }
274
DataSeries >> collectWithNotNils: aBlock [
5✔
275
        "Applies aBlock to every non-nil element"
5✔
276

5✔
277
        ^ self collect: [ :each | each ifNotNil: [ aBlock value: each ] ]
5✔
278
]
5✔
279

280
{ #category : 'math functions' }
281
DataSeries >> correlationWith: otherSeries [
5✔
282
        "Calculate the Pearson correlation coefficient between self and the other series"
5✔
283

5✔
284
        "((#(1 2 4) asDataSeries) correlationWith: (#(2 4 8) asDataSeries)) >>> 1."
5✔
285

5✔
286
        "((#(1 2 4) asDataSeries) correlationWith: (#(-3 -6 -12) asDataSeries)) >>> -1."
5✔
287

5✔
288
        ^ self
5✔
289
                  correlationWith: otherSeries
5✔
290
                  using: DataPearsonCorrelationMethod
5✔
291
]
5✔
292

293
{ #category : 'math functions' }
294
DataSeries >> correlationWith: otherSeries using: aCorrelationCoefficient [
5✔
295
        "Calculate the correlation coefficient between self and the other series using the given method"
5✔
296

5✔
297
        "((#(1 2 4) asDataSeries) correlationWith: (#(2 4 8) asDataSeries) using: DataPearsonCorrelationMethod) >>> 1."
5✔
298

5✔
299
        "((#(1 2 4) asDataSeries) correlationWith: (#(-3 -6 -12) asDataSeries) using: DataPearsonCorrelationMethod) >>> -1."
5✔
300

5✔
301
        ^ aCorrelationCoefficient between: self and: otherSeries
5✔
302
]
5✔
303

304
{ #category : 'statistics' }
305
DataSeries >> countNils [
5✔
306
        "Returns the number of nil values in the data series"
5✔
307

5✔
308
        "(#(1 nil 2 nil nil) asDataSeries countNils) >>> 3"
5✔
309

5✔
310
        "(#('A' 'nil' nil 'B') asDataSeries countNils) >>> 1"
5✔
311

5✔
312
        ^ self count: [ :each | each isNil ]
5✔
313
]
5✔
314

315
{ #category : 'statistics' }
316
DataSeries >> countNonNils [
5✔
317
        "Returns the number of non-nil values in the data series"
5✔
318

5✔
319
        "(#(1 nil 2 nil nil) asDataSeries countNonNils) >>> 2"
5✔
320

5✔
321
        "(#('A' 'nil' nil 'B') asDataSeries countNonNils) >>> 3"
5✔
322

5✔
323
        ^ self count: [ :each | each isNotNil ]
5✔
324
]
5✔
325

326
{ #category : 'statistics' }
327
DataSeries >> crossTabulateWith: aSeries [
5✔
328
        "A DataFrame is returned which is useful in quantitatively analyzing the relationship of values in one data series with the values in another data series"
5✔
329

5✔
330
        | df |
5✔
331
        self size = aSeries size ifFalse: [ SizeMismatch signal ].
5✔
332

5✔
333
        df := DataFrame withRows:
5✔
334
                      (self removeDuplicates sortIfPossible collect: [ :each1 |
5✔
335
                               aSeries removeDuplicates sortIfPossible collect: [ :each2 |
5✔
336
                                       (1 to: self size) inject: 0 into: [ :accum :i |
5✔
337
                                               ((self atIndex: i) = each1 and:
5✔
338
                                                        (aSeries atIndex: i) = each2)
5✔
339
                                                       ifTrue: [ accum + 1 ]
5✔
340
                                                       ifFalse: [ accum ] ] ] ]).
5✔
341

5✔
342
        df rowNames: self removeDuplicates sortIfPossible.
5✔
343
        df columnNames: aSeries removeDuplicates sortIfPossible.
5✔
344
        ^ df
5✔
345
]
5✔
346

347
{ #category : 'statistics' }
348
DataSeries >> cumulativeSum [
5✔
349
        "Calculate the cumulative sum of a data series and return a new data series with keys as self keys and values as cumulative sum"
5✔
350

5✔
351
        "(#(1 nil 2 3 4) asDataSeries cumulativeSum) >>> (#(1 1 3 6 10) asDataSeries)"
5✔
352

5✔
353
        "(#(nil nil 10 90) asDataSeries cumulativeSum) >>> (#(0 0 10 100) asDataSeries)"
5✔
354

5✔
355
        | sum |
5✔
356
        sum := 0.
5✔
357

5✔
358
        ^ self collect: [ :each |
5✔
359
                  each ifNotNil: [ sum := sum + each ].
5✔
360
                  sum ]
5✔
361
]
5✔
362

363
{ #category : 'defaults' }
364
DataSeries >> defaultHeadTailSize [
5✔
365
        ^ 5
5✔
366
]
5✔
367

368
{ #category : 'defaults' }
369
DataSeries >> defaultName [
5✔
370
        ^ '(no name)'
5✔
371
]
5✔
372

373
{ #category : 'defaults' }
374
DataSeries >> defaultPrecision [
5✔
375
        ^ 0.0001
5✔
376
]
5✔
377

378
{ #category : 'accessing' }
379
DataSeries >> eighth [
5✔
380
        "Answer the eighth element of the receiver.
5✔
381
        Raise an error if there are not enough elements."
5✔
382

5✔
383
        "(#(a b c d e f g h i j) asDataSeries eighth) >>> #h"
5✔
384

5✔
385
        ^ self atIndex: 8
5✔
386
]
5✔
387

388
{ #category : 'converting' }
389
DataSeries >> encodeOneHot [
5✔
390
        "Encode the values of the DataSeries into one-hot vectors."
5✔
391

5✔
392
        "(#(a b) asDataSeries encodeOneHot) >>>(#(#(1 0) #(0 1))asDataSeries) "
5✔
393

5✔
394
        "(#(1 2 3) asDataSeries encodeOneHot) >>>(#(#(1 0 0) #(0 1 0) #(0 0 1))asDataSeries) "
5✔
395

5✔
396
        "(#(23 0.5 542) asDataSeries encodeOneHot) >>>(#(#(0 1 0) #(1 0 0) #(0 0 1))asDataSeries) "
5✔
397

5✔
398
        | uniqueValues encodingDataSeries oneHotValues |
5✔
399
        uniqueValues := self removeDuplicates sortIfPossible.
5✔
400
        encodingDataSeries := self class new.
5✔
401
        uniqueValues withIndexDo: [ :value :index |
5✔
402
                encodingDataSeries at: value put: index ].
5✔
403
        oneHotValues := self values collect: [ :value |
5✔
404
                                | oneHot |
5✔
405
                                oneHot := encodingDataSeries keys collect: [ :key |
5✔
406
                                                  value = key
5✔
407
                                                          ifTrue: [ 1 ]
5✔
408
                                                          ifFalse: [ 0 ] ].
5✔
409
                                oneHot ].
5✔
410
        ^ DataSeries withKeys: self keys values: oneHotValues name: self name
5✔
411
]
5✔
412

413
{ #category : 'private' }
414
DataSeries >> errorKeyNotFound: aKey [
×
415

×
416
        KeyNotFound signalFor: aKey
×
417
]
×
418

419
{ #category : 'errors' }
420
DataSeries >> errorKeysMismatch [
5✔
421
        Error signal: 'Keys of two series do not match'
5✔
422
]
5✔
423

424
{ #category : 'accessing' }
425
DataSeries >> fifth [
5✔
426
        "Answer the fifth element of the receiver.
5✔
427
        Raise an error if there are not enough elements."
5✔
428

5✔
429
        "(#(a b c d e f g h i j) asDataSeries fifth) >>> #e"
5✔
430

5✔
431
        ^ self atIndex: 5
5✔
432
]
5✔
433

434
{ #category : 'accessing' }
435
DataSeries >> first [
5✔
436
        "Answer the first element of the receiver.
5✔
437
        Raise an error if there are not enough elements."
5✔
438

5✔
439
        "(#(a b c d e f g h i j) asDataSeries first) >>> #a"
5✔
440

5✔
441
        ^ self atIndex: 1
5✔
442
]
5✔
443

444
{ #category : 'statistics' }
445
DataSeries >> firstQuartile [
5✔
446
        "25% of the values in a set are smaller than or equal to the first Quartile of that set"
5✔
447

5✔
448
        "(#(7 4 20) asDataSeries firstQuartile) >>> 4"
5✔
449

5✔
450
        ^ self quartile: 1
5✔
451
]
5✔
452

453
{ #category : 'accessing' }
454
DataSeries >> fourth [
5✔
455
        "Answer the fourth element of the receiver.
5✔
456
        Raise an error if there are not enough elements."
5✔
457

5✔
458
        "(#(a b c d e f g h i j) asDataSeries fourth) >>> #d"
5✔
459

5✔
460
        ^ self atIndex: 4
5✔
461
]
5✔
462

463
{ #category : 'statistics' }
464
DataSeries >> fourthQuartile [
5✔
465
        "Fourth Quartile is the maximum value in a set of values"
5✔
466

5✔
467
        "(#(7 4 20) asDataSeries fourthQuartile) >>> 20"
5✔
468

5✔
469
        ^ self quartile: 4
5✔
470
]
5✔
471

472
{ #category : 'grouping' }
473
DataSeries >> groupBy: otherSeries aggregateUsing: aBlock [
5✔
474
        "Group my values by the unique values of otherSeries, aggregate them using aBlock. Use my name by default"
5✔
475
        ^ self groupBy: otherSeries aggregateUsing: aBlock as: self name
5✔
476
]
5✔
477

478
{ #category : 'grouping' }
479
DataSeries >> groupBy: otherSeries aggregateUsing: aBlock as: aNewName [
5✔
480
        "Group my values by the unique values of otherSeries, aggregate them using aBlock, and answer a new DataSeries with unique values of otherSeries as keys, aggregated values of myself as values, and aNewName as name"
5✔
481

5✔
482
        | groupMap |
5✔
483
        self size = otherSeries size ifFalse: [ SizeMismatch signal ].
5✔
484

5✔
485
        groupMap := (otherSeries removeDuplicates sortIfPossible collect: [
5✔
486
                             :e | e -> OrderedCollection new ]) asOrderedDictionary.
5✔
487

5✔
488
        1 to: self size do: [ :index |
5✔
489
                (groupMap at: (otherSeries atIndex: index)) add:
5✔
490
                        (self atIndex: index) ].
5✔
491

5✔
492
        ^ self class
5✔
493
                  withKeys: groupMap keys
5✔
494
                  values: (groupMap values collect: aBlock)
5✔
495
                  name: aNewName
5✔
496
]
5✔
497

498
{ #category : 'grouping' }
499
DataSeries >> groupByBins: bins [
5✔
500

5✔
501
        ^ self groupByBins: bins labelled: (1 to: bins size - 1)
5✔
502
]
5✔
503

504
{ #category : 'grouping' }
505
DataSeries >> groupByBins: bins labelled: aCollection [
5✔
506
        "I receive two parameters:
5✔
507
        - A collection of bins that will determine intervals to group the values
5✔
508
        - A collection of labels to apply for each intervals of the bins
5✔
509

5✔
510
        I return a new DataSeries associating each key to a label corresponding to the bin they match."
5✔
511

5✔
512
        | labelledIntervals |
5✔
513
        bins size = (aCollection size + 1) ifFalse: [ SizeMismatch signal: 'The labels should have one less elements than the bins.' ].
5✔
514

5✔
515
        labelledIntervals := OrderedDictionary new.
5✔
516
        bins overlappingPairsWithIndexDo: [ :min :max :index | labelledIntervals at: (aCollection at: index) put: min -> max ].
5✔
517

5✔
518
        ^ self collect: [ :each | labelledIntervals keyAtValue: (labelledIntervals values detect: [ :asso | each between: asso key and: asso value ]) ]
5✔
519
]
5✔
520

521
{ #category : 'grouping' }
522
DataSeries >> groupByUniqueValuesAndAggregateUsing: aBlock [
5✔
523
        "Group my values by their unique values and aggregate them using aBlock. Use my name by default"
5✔
524
        ^ self groupByUniqueValuesAndAggregateUsing: aBlock as: self name
5✔
525
]
5✔
526

527
{ #category : 'grouping' }
528
DataSeries >> groupByUniqueValuesAndAggregateUsing: aBlock as: aNewName [
5✔
529
        "Group my values by unique values, aggregate them using aBlock, and answer a new DataSeries with theunique values as keys, aggregated values of myself as values, and aNewName as name"
5✔
530

5✔
531
        | groupMap |
5✔
532
        groupMap := (self removeDuplicates sortIfPossible collect: [ :e |
5✔
533
                             e -> OrderedCollection new ]) asOrderedDictionary.
5✔
534

5✔
535
        self do: [ :each | (groupMap at: each) add: each ].
5✔
536

5✔
537
        ^ self class
5✔
538
                  withKeys: groupMap keys
5✔
539
                  values: (groupMap values collect: aBlock)
5✔
540
                  name: aNewName
5✔
541
]
5✔
542

543
{ #category : 'testing' }
544
DataSeries >> hasNil [
5✔
545
        "return true if data series has at least one nil value"
5✔
546

5✔
547
        "(#(a nil b) asDataSeries hasNil) >>> true"
5✔
548

5✔
549
        "(#(a 'nil' b) asDataSeries hasNil) >>> false"
5✔
550

5✔
551
        "(#(1 nil 3) asDataSeries hasNil) >>> true"
5✔
552

5✔
553
        "(#(1 0 3) asDataSeries hasNil) >>> false"
5✔
554

5✔
555
        ^ self includes: nil
5✔
556
]
5✔
557

558
{ #category : 'slicing' }
559
DataSeries >> head [
5✔
560
        "Returns a data series with first 5 elements of the receiver"
5✔
561

5✔
562
        "(#(a b c d e f g h i j) asDataSeries head) >>> (#(a b c d e) asDataSeries)"
5✔
563

5✔
564
        "(#(1 2 3 4 5 6 7 8 9 10) asDataSeries head) >>> (#(1 2 3 4 5) asDataSeries)"
5✔
565

5✔
566
        ^ self head: self defaultHeadTailSize
5✔
567
]
5✔
568

569
{ #category : 'slicing' }
570
DataSeries >> head: aNumber [
5✔
571
        "Returns a data series with first aNumber elements of the receiver"
5✔
572

5✔
573
        "(#(a b c d e f g h i j) asDataSeries head: 3) >>> (#(a b c) asDataSeries)"
5✔
574

5✔
575
        "(#(1 2 3 4 5 6 7 8 9 10) asDataSeries head: 1) >>> (#(1) asDataSeries)"
5✔
576

5✔
577
        ^ self species
5✔
578
                  withKeys: (self keys copyFrom: 1 to: aNumber)
5✔
579
                  values: (self values copyFrom: 1 to: aNumber)
5✔
580
                  name: self name
5✔
581
]
5✔
582

583
{ #category : 'initialization' }
584
DataSeries >> initialize [
5✔
585
        super initialize.
5✔
586
        name := self defaultName
5✔
587
]
5✔
588

589
{ #category : 'initialization' }
590
DataSeries >> initialize: aCapacity [
5✔
591
        "Make sure that initialize is called and the default name is set"
5✔
592
        self initialize.
5✔
593
        ^ super initialize: aCapacity
5✔
594
]
5✔
595

596
{ #category : 'statistics' }
597
DataSeries >> interquartileRange [
5✔
598
        "The Inter Quartile Range is the difference between the third Quartile and the first Quartile"
5✔
599

5✔
600
        "(#(7 4 20) asDataSeries interquartileRange) >>> 16"
5✔
601

5✔
602
        ^ self thirdQuartile - self firstQuartile
5✔
603
]
5✔
604

605
{ #category : 'categorical-numerical' }
606
DataSeries >> isCategorical [
5✔
607
        "Returns true if atleast one value of the data series is non numerical and returns false otherwise"
5✔
608

5✔
609
        "(#(a 1 2 3) asDataSeries isCategorical) >>> true"
5✔
610

5✔
611
        "(#(0 1 2 3) asDataSeries isCategorical) >>> false"
5✔
612

5✔
613
        "(#(a b c d) asDataSeries isCategorical) >>> true"
5✔
614

5✔
615
        ^ self isNumerical not
5✔
616
]
5✔
617

618
{ #category : 'categorical-numerical' }
619
DataSeries >> isNumerical [
5✔
620
        "Returns true if all values of the data series are numerical values and returns false otherwise"
5✔
621

5✔
622
        "(#(a 1 2 3) asDataSeries isNumerical) >>> false"
5✔
623

5✔
624
        "(#(0 1 2.2 3) asDataSeries isNumerical) >>> true"
5✔
625

5✔
626
        "((#( I XIV VII XII ) collect: [ :each | each romanNumber ]) asDataSeries isNumerical) >>> true"
5✔
627

5✔
628
        ^ forcedIsNumerical ifNil: [
5✔
629
                  (self removeDuplicates copyWithout: nil) allSatisfy: [ :each |
5✔
630
                          each isNumber ] ]
5✔
631
]
5✔
632

633
{ #category : 'testing' }
634
DataSeries >> isSequenceable [
5✔
635
        ^ true
5✔
636
]
5✔
637

638
{ #category : 'private' }
639
DataSeries >> keys: anArrayOfKeys [
5✔
640
        | keys |
5✔
641
        keys := anArrayOfKeys asArray deepCopy.
5✔
642
        dictionary := self dictionaryClass newFromKeys: keys andValues: self values.
5✔
643
        orderedKeys := keys
5✔
644
]
5✔
645

646
{ #category : 'accessing' }
647
DataSeries >> last [
5✔
648
        "Answer the last element of the receiver.
5✔
649
        Raise an error if there are not enough elements."
5✔
650

5✔
651
        "(#(a b c d e f g h i j) asDataSeries last) >>> #j"
5✔
652

5✔
653
        ^ self atIndex: self size
5✔
654
]
5✔
655

656
{ #category : 'math functions' }
657
DataSeries >> log: base [
5✔
658
        "Returns a data series containing the logarithm of each value in the receiver using the specified base."
5✔
659

5✔
660
        "(#(1 2 4 8 16) asDataSeries log: 2) >>> (#(0.0 1.0 2.0 3.0 4.0) asDataSeries)"
5✔
661

5✔
662
        "(#(1 10 100) asDataSeries log: 10) >>> (#(0.0 1.0 2.0) asDataSeries)"
5✔
663

5✔
664
        ^ self collect: [ :each | each log: base ]
5✔
665
]
5✔
666

667
{ #category : 'categorical-numerical' }
668
DataSeries >> makeCategorical [
5✔
669
        "Converts a data series to a categorical data series"
5✔
670
        
5✔
671
        forcedIsNumerical := false
5✔
672
]
5✔
673

674
{ #category : 'categorical-numerical' }
675
DataSeries >> makeNumerical [
5✔
676
        "Converts a data series to a numerical data series"
5✔
677

5✔
678
        forcedIsNumerical := true
5✔
679
]
5✔
680

681
{ #category : 'statistics' }
682
DataSeries >> max [
5✔
683
        "Returns the maximum value of the dataseries without including nils"
5✔
684

5✔
685
        "(#(7 4 20) asDataSeries max) >>> 20"
5✔
686

5✔
687
        ^ self removeNils values max
5✔
688
]
5✔
689

690
{ #category : 'statistics' }
691
DataSeries >> median [
5✔
692
        "Returns the median without including nils"
5✔
693

5✔
694
        "(#(7 4 20) asDataSeries median) >>> 7"
5✔
695

5✔
696
        ^ self removeNils values median
5✔
697
]
5✔
698

699
{ #category : 'statistics' }
700
DataSeries >> min [
5✔
701
        "Returns the minimum value of the dataseries without including nils"
5✔
702

5✔
703
        "(#(7 4 20) asDataSeries min) >>> 4"
5✔
704

5✔
705
        ^ self removeNils values min
5✔
706
]
5✔
707

708
{ #category : 'accessing' }
709
DataSeries >> mode [
5✔
710
        "The mode of a set of values is the value that appears most often. "
5✔
711

5✔
712
        "(#(a j a j e j g j i j) asDataSeries mode) >>> #j"
5✔
713

5✔
714
        "(#(1 2 3 2) asDataSeries mode) >>> 2"
5✔
715

5✔
716
        | valueCounts |
5✔
717
        valueCounts := self valueCounts.
5✔
718
        ^ valueCounts keyAtValue: valueCounts max
5✔
719
]
5✔
720

721
{ #category : 'accessing' }
722
DataSeries >> name [
5✔
723
        "Answer the name of the receiver"
5✔
724
        
5✔
725
        ^ name
5✔
726
]
5✔
727

728
{ #category : 'accessing' }
729
DataSeries >> name: anObject [
5✔
730
        "Set the name of the receiver to anObject"
5✔
731
        
5✔
732
        name := anObject
5✔
733
]
5✔
734

735
{ #category : 'accessing' }
736
DataSeries >> ninth [
5✔
737
        "Answer the ninth element of the receiver.
5✔
738
        Raise an error if there are not enough elements."
5✔
739

5✔
740
        "(#(a b c d e f g h i j) asDataSeries ninth) >>> #i"
5✔
741

5✔
742
        ^ self atIndex: 9
5✔
743
]
5✔
744

745
{ #category : 'statistics' }
746
DataSeries >> quantile: aNumber [
5✔
747
        "A quantile determines how many values in a distribution are above or below a certain limit.
5✔
748
Eg: if the parameter aNumber is 85, a value from the data series is returned which is greater than or equal to 85% of the values in the data series"
5✔
749

5✔
750
        "(#(7 4 20) asDataSeries quantile: 50) >>> 7"
5✔
751

5✔
752
        | sortedSeries index |
5✔
753
        sortedSeries := self withoutNils sorted.
5✔
754

5✔
755
        aNumber = 0 ifTrue: [ ^ sortedSeries first ].
5✔
756

5✔
757
        index := (sortedSeries size * (aNumber / 100)) ceiling.
5✔
758
        ^ sortedSeries atIndex: index
5✔
759
]
5✔
760

761
{ #category : 'statistics' }
762
DataSeries >> quartile: aNumber [
5✔
763
        "Quartiles are three values that split sorted data into four parts, each with an equal number of observations.
5✔
764
Eg: if the parameter aNumber is 3, the Third Quartile of the data series is returned"
5✔
765

5✔
766
        "(#(7 4 20) asDataSeries quartile: 3) >>> 20"
5✔
767

5✔
768
        ^ self quantile: 25 * aNumber
5✔
769
]
5✔
770

771
{ #category : 'enumerating' }
772
DataSeries >> reject: aBlock [
5✔
773
        | result |
5✔
774
        result := super reject: aBlock.
5✔
775
        result name: self name.
5✔
776
        ^ result
5✔
777
]
5✔
778

779
{ #category : 'removing' }
780
DataSeries >> removeAt: aKey [
5✔
781
        "Removes element from the data series with key aKey"
5✔
782

5✔
783
        ^ self removeKey: aKey
5✔
784
]
5✔
785

786
{ #category : 'removing' }
787
DataSeries >> removeAtIndex: aNumber [
5✔
788
        "Removes element from the data series with index aNumber"
5✔
789

5✔
790
        ^ self removeAt: (self keys at: aNumber)
5✔
791
]
5✔
792

793
{ #category : 'removing' }
794
DataSeries >> removeDuplicates [
5✔
795
        "Answer the unique values of the receiver by removing duplicates"
5✔
796

5✔
797
        "(#(1 2 3 3 2) asDataSeries removeDuplicates) >>> (#(1 2 3))"
5✔
798

5✔
799
        "(#(c d b c d d) asDataSeries removeDuplicates) >>> (#(#c #d #b))"
5✔
800

5✔
801
        ^ self asSet asArray
5✔
802
]
5✔
803

804
{ #category : 'removing' }
805
DataSeries >> removeNils [
5✔
806
        "Removes elements with nil values from the data series"
5✔
807

5✔
808
        "(#(nil 1 nil nil 2) asDataSeries removeNils) >>> (DataSeries withKeys: #(2 5) values: #(1 2))"
5✔
809

5✔
810
        "(#(a b 'nil' nil nil nil) asDataSeries removeNils) >>> (#(a b 'nil') asDataSeries)"
5✔
811

5✔
812
        | keysWithNilValues |
5✔
813
        keysWithNilValues := OrderedCollection new.
5✔
814
        self associationsDo: [ :each |
5✔
815
                each value ifNil: [ keysWithNilValues add: each key ] ].
5✔
816
        self removeKeys: keysWithNilValues
5✔
817
]
5✔
818

819
{ #category : 'replacing' }
820
DataSeries >> replaceNilsWith: anObject [
5✔
821
        "Replaces nils inplace with anObject"
5✔
822

5✔
823
        "(#(a 'nil' nil d nil) asDataSeries replaceNilsWith: #b) >>> (#(a 'nil' b d b) asDataSeries)"
5✔
824

5✔
825
        "(#(1 0 nil 3 nil) asDataSeries replaceNilsWith: 7) >>> (#(1 0 7 3 7) asDataSeries)"
5✔
826

5✔
827
        self withIndexDo: [ :ele :index |
5✔
828
                ele ifNil: [ self atIndex: index put: anObject ] ]
5✔
829
]
5✔
830

831
{ #category : 'replacing' }
832
DataSeries >> replaceNilsWithAverage [
5✔
833
        "Replaces nils inplace with average"
5✔
834

5✔
835
        "(#(1 2 nil 3 nil) asDataSeries replaceNilsWithAverage) >>> (#(1 2 2 3 2) asDataSeries)"
5✔
836

5✔
837
        "(#(3 6 2 9 nil) asDataSeries replaceNilsWithAverage) >>> (#(3 6 2 9 5) asDataSeries)"
5✔
838

5✔
839
        | mean |
5✔
840
        mean := (self select: [ :ele | ele isNotNil ]) average.
5✔
841
        self replaceNilsWith: mean
5✔
842
]
5✔
843

844
{ #category : 'replacing' }
845
DataSeries >> replaceNilsWithMedian [
5✔
846
        "Replaces nils inplace with median"
5✔
847

5✔
848
        "(#(1 2 nil 3) asDataSeries replaceNilsWithMedian) >>> (#(1 2 2 3) asDataSeries)"
5✔
849

5✔
850
        "(#(3 7 nil 9 nil) asDataSeries replaceNilsWithMedian) >>> (#(3 7 7 9 7) asDataSeries)"
5✔
851

5✔
852
        | median |
5✔
853
        median := (self select: [ :ele | ele isNotNil ]) median.
5✔
854
        self replaceNilsWith: median
5✔
855
]
5✔
856

857
{ #category : 'replacing' }
858
DataSeries >> replaceNilsWithMode [
5✔
859
        "Replaces nils inplace with mode"
5✔
860

5✔
861
        "(#(1 2 1 3 nil) asDataSeries replaceNilsWithMode) >>> (#(1 2 1 3 1) asDataSeries)"
5✔
862

5✔
863
        "(#(a a a b nil) asDataSeries replaceNilsWithMode) >>> (#(a a a b a) asDataSeries)"
5✔
864

5✔
865
        | mode |
5✔
866
        mode := (self select: [ :ele | ele isNotNil ]) mode.
5✔
867
        self replaceNilsWith: mode
5✔
868
]
5✔
869

870
{ #category : 'replacing' }
871
DataSeries >> replaceNilsWithPreviousValue [
5✔
872
        "Replaces nils inplace with previous non-nil value"
5✔
873

5✔
874
        "(#(nil 2 nil 3 nil) asDataSeries replaceNilsWithPreviousValue) >>> (#(nil 2 2 3 3) asDataSeries)"
5✔
875

5✔
876
        "(#(a nil b c nil) asDataSeries replaceNilsWithPreviousValue) >>> (#(a a b c c) asDataSeries)"
5✔
877

5✔
878
        | value |
5✔
879
        self withIndexDo: [ :ele :index |
5✔
880
                index > 1 ifTrue: [ ele ifNil: [ self atIndex: index put: value ] ].
5✔
881
                value := self atIndex: index ]
5✔
882
]
5✔
883

884
{ #category : 'replacing' }
885
DataSeries >> replaceNilsWithZeros [
5✔
886
        "Replaces nils inplace with zero"
5✔
887

5✔
888
        "(#(1 2 nil 3 nil) asDataSeries replaceNilsWithZeros) >>> (#(1 2 0 3 0) asDataSeries)"
5✔
889

5✔
890
        "(#(a b c d nil) asDataSeries replaceNilsWithZeros) >>> (#(a b c d 0) asDataSeries)"
5✔
891

5✔
892
        self replaceNilsWith: 0
5✔
893
]
5✔
894

895
{ #category : 'accessing' }
896
DataSeries >> second [
5✔
897
        "Answer the second element of the receiver.
5✔
898
        Raise an error if there are not enough elements."
5✔
899

5✔
900
        "(#(a b c d e f g h i j) asDataSeries second) >>> #b"
5✔
901

5✔
902
        ^ self atIndex: 2
5✔
903
]
5✔
904

905
{ #category : 'statistics' }
906
DataSeries >> secondQuartile [
5✔
907
        "50% of the values in a set are smaller than or equal to the second Quartile of that set. It is also known as the median"
5✔
908

5✔
909
        "(#(7 4 20) asDataSeries secondQuartile) >>> 7"
5✔
910

5✔
911
        ^ self quartile: 2
5✔
912
]
5✔
913

914
{ #category : 'enumerating' }
915
DataSeries >> select: aBlock [
5✔
916
        | result |
5✔
917
        result := super select: aBlock.
5✔
918
        result name: self name.
5✔
919
        ^ result
5✔
920
]
5✔
921

922
{ #category : 'accessing' }
923
DataSeries >> seventh [
5✔
924
        "Answer the seventh element of the receiver.
5✔
925
        Raise an error if there are not enough elements."
5✔
926

5✔
927
        "(#(a b c d e f g h i j) asDataSeries seventh) >>> #g"
5✔
928

5✔
929
        ^ self atIndex: 7
5✔
930
]
5✔
931

932
{ #category : 'accessing' }
933
DataSeries >> sixth [
5✔
934
        "Answer the sixth element of the receiver.
5✔
935
        Raise an error if there are not enough elements."
5✔
936

5✔
937
        "(#(a b c d e f g h i j) asDataSeries sixth) >>> #f"
5✔
938

5✔
939
        ^ self atIndex: 6
5✔
940
]
5✔
941

942
{ #category : 'sorting' }
943
DataSeries >> sort [
5✔
944
        "Arranges a data series in ascending order of its values"
5✔
945

5✔
946
        "(#(a c b) asDataSeries sort) >>> (DataSeries withKeys: #(1 3 2) values: #(a b c))"
5✔
947

5✔
948
        "(#(500 5 37) asDataSeries sort) >>> (DataSeries withKeys: #(2 3 1) values: #(5 37 500))"
5✔
949

5✔
950
        self sort: [ :a :b | a <= b ]
5✔
951
]
5✔
952

953
{ #category : 'sorting' }
954
DataSeries >> sort: aBlock [
5✔
955
        "Arranges a data series by applying aBlock on its values"
5✔
956

5✔
957
        "(#( z aaa cc ) asDataSeries sort: [ :a :b |
5✔
958
                 a asString size < b asString size ])
5✔
959
        >>> (DataSeries withKeys: #( 1 3 2 ) values: #( z cc aaa ))."
5✔
960

5✔
961
        "(#( 500 5 37 ) asDataSeries sort: [ :a :b | a >= b ])
5✔
962
        >>> (DataSeries withKeys: #( 1 3 2 ) values: #( 500 37 5 ))"
5✔
963

5✔
964
        | associationBlock |
5✔
965
        associationBlock := [ :a :b | aBlock value: a value value: b value ].
5✔
966
        self sortAssociations: associationBlock
5✔
967
]
5✔
968

969
{ #category : 'sorting' }
970
DataSeries >> sortAssociations: aBlock [
5✔
971
        | sortedAssociations |
5✔
972
        sortedAssociations := self associations sort: aBlock.
5✔
973
        self removeAll.
5✔
974
        self addAll: sortedAssociations
5✔
975
]
5✔
976

977
{ #category : 'sorting' }
978
DataSeries >> sortDescending [
5✔
979
        "Arranges a data series in descending order of its values"
5✔
980

5✔
981
        "(#(a c b) asDataSeries sortDescending) >>> (DataSeries withKeys: #(2 3 1) values: #(c b a))"
5✔
982

5✔
983
        "(#(500 5 37) asDataSeries sortDescending) >>> (DataSeries withKeys: #(1 3 2) values: #(500 37 5))"
5✔
984

5✔
985
        self sort: [ :a :b | a > b ]
5✔
986
]
5✔
987

988
{ #category : 'sorting' }
989
DataSeries >> sorted [
5✔
990
        "Returns a sorted copy of the data series without rearranging the original data series"
5✔
991

5✔
992
        "(#(a c b) asDataSeries sorted) >>> (DataSeries withKeys: #(1 3 2) values: #(a b c))"
5✔
993

5✔
994
        "(#(500 5 37) asDataSeries sorted) >>> (DataSeries withKeys: #(2 3 1) values: #(5 37 500))"
5✔
995

5✔
996
        ^ self sorted: [ :a :b | a <= b ]
5✔
997
]
5✔
998

999
{ #category : 'sorting' }
1000
DataSeries >> sorted: aBlock [
5✔
1001
        "Returns a copy of the data series after applying aBlock without rearranging the original data series"
5✔
1002

5✔
1003
        "(#( z aaa cc ) asDataSeries sorted: [ :a :b |
5✔
1004
                 a asString size < b asString size ])
5✔
1005
        >>> (DataSeries withKeys: #( 1 3 2 ) values: #( z cc aaa ))."
5✔
1006

5✔
1007
        "(#( 500 5 37 ) asDataSeries sorted: [ :a :b | a >= b ])
5✔
1008
        >>> (DataSeries withKeys: #( 1 3 2 ) values: #( 500 37 5 ))"
5✔
1009

5✔
1010
        | associationBlock |
5✔
1011
        associationBlock := [ :a :b | aBlock value: a value value: b value ].
5✔
1012
        ^ self sortedAssociations: associationBlock
5✔
1013
]
5✔
1014

1015
{ #category : 'sorting' }
1016
DataSeries >> sortedAssociations: aBlock [
5✔
1017
        | sortedAssociations |
5✔
1018
        sortedAssociations := self associations sort: aBlock.
5✔
1019
        ^ sortedAssociations asDataSeries name: self name; yourself
5✔
1020
]
5✔
1021

1022
{ #category : 'sorting' }
1023
DataSeries >> sortedDescending [
5✔
1024
        "Returns a sorted copy of the data series in descending order without rearranging the original data series"
5✔
1025

5✔
1026
        "(#(a c b) asDataSeries sortedDescending) >>> (DataSeries withKeys: #(2 3 1) values: #(c b a))"
5✔
1027

5✔
1028
        "(#(50 5 37) asDataSeries sortedDescending) >>> (DataSeries withKeys: #(1 3 2) values: #(50 37 5))"
5✔
1029

5✔
1030
        ^ self sorted: [ :a :b | a > b ]
5✔
1031
]
5✔
1032

1033
{ #category : 'statistics' }
1034
DataSeries >> stdev [
5✔
1035
        "Returns the standard deviation of the dataseries without including nils"
5✔
1036

5✔
1037
        "(#(10 20 30) asDataSeries stdev) >>> 10"
5✔
1038

5✔
1039
        ^ self removeNils values stdev
5✔
1040
]
5✔
1041

1042
{ #category : 'transformation' }
1043
DataSeries >> sum [
5✔
1044
        "Return the sum of the values over the requested axis. Nil values are excluded."
5✔
1045

5✔
1046
        "(#(1 1 1) asDataSeries sum) >>> 3"
5✔
1047

5✔
1048
        "(#(1 nil 1) asDataSeries sum) >>> 2"
5✔
1049

5✔
1050
        "(#(1 1.1 1) asDataSeries sum) >>> 3.1"
5✔
1051

5✔
1052
        | result |
5✔
1053
        result := 0.
5✔
1054
        self do: [ :each | each ifNotNil: [ result := result + each ] ].
5✔
1055
        ^ result
5✔
1056
]
5✔
1057

1058
{ #category : 'statistics' }
1059
DataSeries >> summary [
5✔
1060
        "A data series is returned which is a statistical summary of the data series. 
5✔
1061
        With keys as different statistical measures and values as the values returned
5✔
1062
        when those statistical measures are applied on the data series."
5✔
1063
        
5✔
1064
        | summary |
5✔
1065
        summary := self species new.
5✔
1066
        summary name: self name.
5✔
1067

5✔
1068
        summary
5✔
1069
                at: 'Count' put: self size;
5✔
1070
                at: 'Average' put: self average;
5✔
1071
                at: 'Stdev' put: self stdev;
5✔
1072
                at: 'Min' put: self min;
5✔
1073
                at: '25%' put: self firstQuartile;
5✔
1074
                at: '50%' put: self median;
5✔
1075
                at: '75%' put: self thirdQuartile;
5✔
1076
                at: 'Max' put: self max.
5✔
1077

5✔
1078
        ^ summary
5✔
1079
]
5✔
1080

1081
{ #category : 'slicing' }
1082
DataSeries >> tail [
5✔
1083
        "Returns a data series with last 5 elements of the receiver"
5✔
1084

5✔
1085
        "(#(a b c d e f) asDataSeries tail) >>> (DataSeries withKeys: #(2 3 4 5 6) values: #(b c d e f) )"
5✔
1086

5✔
1087
        "(#(1 2 3 4 5 6 7) asDataSeries tail) >>> (DataSeries withKeys: #(3 4 5 6 7) values: #(3 4 5 6 7) )"
5✔
1088

5✔
1089
        ^ self tail: self defaultHeadTailSize
5✔
1090
]
5✔
1091

1092
{ #category : 'slicing' }
1093
DataSeries >> tail: aNumber [
5✔
1094
        "Returns a data series with last aNumber elements of the receiver"
5✔
1095

5✔
1096
        "(#(a b c d e f) asDataSeries tail: 3) >>> (DataSeries withKeys: #(4 5 6) values: #(d e f) )"
5✔
1097

5✔
1098
        "(#(1 2 3 4 5 6 7) asDataSeries tail: 2) >>> (DataSeries withKeys: #(6 7) values: #(6 7) )"
5✔
1099

5✔
1100
        ^ self species
5✔
1101
                  withKeys:
5✔
1102
                  (self keys copyFrom: self size - aNumber + 1 to: self size)
5✔
1103
                  values:
5✔
1104
                  (self values copyFrom: self size - aNumber + 1 to: self size)
5✔
1105
                  name: self name
5✔
1106
]
5✔
1107

1108
{ #category : 'accessing' }
1109
DataSeries >> third [
5✔
1110
        "Answer the third element of the receiver.
5✔
1111
        Raise an error if there are not enough elements."
5✔
1112

5✔
1113
        "(#(a b c d e f g h i j) asDataSeries third) >>> #c"
5✔
1114

5✔
1115
        ^ self atIndex: 3
5✔
1116
]
5✔
1117

1118
{ #category : 'statistics' }
1119
DataSeries >> thirdQuartile [
5✔
1120
        "75% of the values in a set are smaller than or equal to the third Quartile of that set"
5✔
1121

5✔
1122
        "(#(7 4 20) asDataSeries thirdQuartile) >>> 20"
5✔
1123

5✔
1124
        ^ self quartile: 3
5✔
1125
]
5✔
1126

1127
{ #category : 'accessing' }
1128
DataSeries >> uniqueValues [
×
1129

×
1130
        self
×
1131
                deprecated:
×
1132
                'The name of this method has been changed to removeDuplicates.'
×
1133
                transformWith:
×
1134
                '`@receiver uniqueValues' -> '`@receiver removeDuplicates'.
×
1135
        ^ self removeDuplicates
×
1136
]
×
1137

1138
{ #category : 'statistics' }
1139
DataSeries >> valueCounts [
5✔
1140
        "Calculates the frequency of each value in the data series and returns a data series in descending order of frequencies"
5✔
1141

5✔
1142
        "(#(7 20 20) asDataSeries valueCounts) >>> (DataSeries withKeys: #(20 7) values: #(2 1))"
5✔
1143

5✔
1144
        ^ (self groupByUniqueValuesAndAggregateUsing: #size) sortDescending
5✔
1145
]
5✔
1146

1147
{ #category : 'statistics' }
1148
DataSeries >> valueFrequencies [
5✔
1149
        "Calculates the relative frequency of values in the data series. Relative frequency is the ratio of the number of times a value occurs in a set to the total number of values in the set"
5✔
1150

5✔
1151
        "(#(7 20 20) asDataSeries valueFrequencies) >>> (DataSeries withKeys: #( 20 7 ) values: {
5✔
1152
                        (2 / 3). (1 / 3) })"
5✔
1153

5✔
1154
        | count freq |
5✔
1155
        count := self valueCounts.
5✔
1156
        freq := count / self size.
5✔
1157
        ^ freq
5✔
1158
]
5✔
1159

1160
{ #category : 'enumerating' }
1161
DataSeries >> with: aCollection collect: twoArgBlock [
5✔
1162
        "Collect and return the result of evaluating twoArgBlock with corresponding elements from this series and aCollection."
5✔
1163
        | result |
5✔
1164
        aCollection size = self size ifFalse: [self errorSizeMismatch].
5✔
1165

5✔
1166
        result := self species new: self size.
5✔
1167
        result name: self name.
5✔
1168

5✔
1169
        self keys withIndexDo: [ :key :i |
5✔
1170
                result at: key put:
5✔
1171
                (twoArgBlock
5✔
1172
                        value: (self at: key)
5✔
1173
                        value: (aCollection at: i))].
5✔
1174
        ^ result
5✔
1175
]
5✔
1176

1177
{ #category : 'enumerating' }
1178
DataSeries >> withIndexCollect: aBlock [
5✔
1179
        | result |
5✔
1180
        result := self species newFrom:
5✔
1181
                (self associations withIndexCollect: [:each :i |
5✔
1182
                        each key -> (aBlock value: each value value: i)]).
5✔
1183
        result name: self name.
5✔
1184
        ^ result
5✔
1185
]
5✔
1186

1187
{ #category : 'enumerating' }
1188
DataSeries >> withIndexDetect: aBlock [
5✔
1189

5✔
1190
        ^ self withIndexDetect: aBlock ifNone: [ NotFound signal ]
5✔
1191
]
5✔
1192

1193
{ #category : 'enumerating' }
1194
DataSeries >> withIndexDetect: aBlock ifNone: exceptionBlock [
5✔
1195

5✔
1196
        | selectedIndex |
5✔
1197

5✔
1198
        selectedIndex := (1 to: self size)
5✔
1199
                detect: [ :i | aBlock value: (self atIndex: i) value: i ]
5✔
1200
                ifNone: [ ^ exceptionBlock value ].
5✔
1201

5✔
1202
        ^ self atIndex: selectedIndex
5✔
1203
]
5✔
1204

1205
{ #category : 'enumerating' }
1206
DataSeries >> withIndexDo: aBlock [
5✔
1207
        self keys withIndexDo: [ :each :i | aBlock value: (self at: each) value: i ]
5✔
1208
]
5✔
1209

1210
{ #category : 'enumerating' }
1211
DataSeries >> withIndexReject: aBlock [
5✔
1212
        ^ self withIndexSelect: [ :each :i | (aBlock value: each value: i) not ]
5✔
1213
]
5✔
1214

1215
{ #category : 'enumerating' }
1216
DataSeries >> withIndexSelect: aBlock [
5✔
1217
        | selectedIndices |
5✔
1218

5✔
1219
        selectedIndices := (1 to: self size) select: [ :i |
5✔
1220
                aBlock value: (self atIndex: i) value: i ].
5✔
1221

5✔
1222
        ^ DataSeries
5✔
1223
                withKeys: (selectedIndices collect: [ :i | self keys at: i ])
5✔
1224
                values: (selectedIndices collect: [ :i | self atIndex: i ])
5✔
1225
                name: self name
5✔
1226
]
5✔
1227

1228
{ #category : 'enumerating' }
1229
DataSeries >> withKeyCollect: aBlock [
5✔
1230
        | result |
5✔
1231
        result := self species newFrom:
5✔
1232
                (self associations collect: [:each |
5✔
1233
                        each key -> (aBlock value: each value value: each key)]).
5✔
1234
        result name: self name.
5✔
1235
        ^ result
5✔
1236
]
5✔
1237

1238
{ #category : 'enumerating' }
1239
DataSeries >> withKeyDetect: aBlock [
5✔
1240

5✔
1241
        ^ self withKeyDetect: aBlock ifNone: [ NotFound signal ]
5✔
1242
]
5✔
1243

1244
{ #category : 'enumerating' }
1245
DataSeries >> withKeyDetect: aBlock ifNone: exceptionBlock [
5✔
1246

5✔
1247
        | selectedKey |
5✔
1248

5✔
1249
        selectedKey := self keys
5✔
1250
                detect: [ :key | aBlock value: (self at: key) value: key ]
5✔
1251
                ifNone: [ ^ exceptionBlock value ].
5✔
1252

5✔
1253
        ^ self at: selectedKey
5✔
1254
]
5✔
1255

1256
{ #category : 'enumerating' }
1257
DataSeries >> withKeyDo: aBlock [
5✔
1258
        self keysDo: [ :each | aBlock value: (self at: each) value: each ]
5✔
1259
]
5✔
1260

1261
{ #category : 'enumerating' }
1262
DataSeries >> withKeyReject: aBlock [
5✔
1263
        ^ self withKeySelect: [ :each :key | (aBlock value: each value: key) not ]
5✔
1264
]
5✔
1265

1266
{ #category : 'enumerating' }
1267
DataSeries >> withKeySelect: aBlock [
5✔
1268
        | selectedKeys |
5✔
1269

5✔
1270
        selectedKeys := self keys select: [ :key |
5✔
1271
                aBlock value: (self at: key) value: key ].
5✔
1272

5✔
1273
        ^ DataSeries
5✔
1274
                withKeys: selectedKeys
5✔
1275
                values: (selectedKeys collect: [ :key | self at: key ])
5✔
1276
                name: self name
5✔
1277
]
5✔
1278

1279
{ #category : 'enumerating' }
1280
DataSeries >> withSeries: otherDataSeries collect: twoArgBlock [
5✔
1281
        "Collect and return the result of evaluating twoArgBlock with corresponding elements from this series and otherDataSeries."
5✔
1282
        | result |
5✔
1283
        otherDataSeries size = self size ifFalse: [self errorSizeMismatch].
5✔
1284
        otherDataSeries keys = self keys ifFalse: [ self errorKeysMismatch ].
5✔
1285

5✔
1286
        result := self species new: self size.
5✔
1287
        result name: self name.
5✔
1288

5✔
1289
        self keysDo: [ :key |
5✔
1290
                result at: key put:
5✔
1291
                (twoArgBlock
5✔
1292
                        value: (self at: key)
5✔
1293
                        value: (otherDataSeries at: key))].
5✔
1294
        ^ result
5✔
1295
]
5✔
1296

1297
{ #category : 'private' }
1298
DataSeries >> withoutNils [
5✔
1299
        "Returns a copy of the data series without the nil values"
5✔
1300

5✔
1301
        "(#(nil 1 nil nil 2) asDataSeries withoutNils) >>> (DataSeries withKeys: #(2 5) values: #(1 2))"
5✔
1302

5✔
1303
        "(#(a b 'nil' nil nil nil) asDataSeries withoutNils) >>> (#(a b 'nil') asDataSeries)"
5✔
1304

5✔
1305
        ^ self reject: #isNil
5✔
1306
]
5✔
1307

1308
{ #category : 'statistics' }
1309
DataSeries >> zerothQuartile [
5✔
1310
        "Zeroth Quartile is the minimum value in a set of values"
5✔
1311

5✔
1312
        "(#(7 4 20) asDataSeries zerothQuartile) >>> 4"
5✔
1313

5✔
1314
        ^ self quartile: 0
5✔
1315
]
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc