@@ -11,8 +11,32 @@ import org.jetbrains.kotlinx.dataframe.samples.api.name
1111import org.jetbrains.kotlinx.dataframe.samples.api.weight
1212import org.junit.Test
1313
14+ /* *
15+ * Tests the behavior of the `first` (`firstCol`) and `firstOrNull` functions, including:
16+ *
17+ * - **ColumnsSelectionDsl**: selecting the first column or first column matching a condition, with invocations
18+ * on illegal types, and in case when no column matches the condition.
19+ *
20+ * - **DataColumn**: getting the first value or the first value matching a predicate,
21+ * verifying behavior on empty columns, columns with `null` values, and columns without values matching the predicate.
22+ *
23+ * - **DataFrame**: getting the first row or first matching row,
24+ * verifying behavior on empty DataFrames, DataFrames with `null` values, and
25+ * DataFrames without rows matching the predicate.
26+ *
27+ * - **GroupBy**: reducing each group to its first row or the first row matching a predicate,
28+ * with handling groups that contain no matching rows.
29+ *
30+ * - **Pivot**: reducing each group in the pivot to its first row or the first row matching a predicate,
31+ * with handling groups that contain no matching rows.
32+ *
33+ * - **PivotGroupBy**: reducing each combined [pivot] + [groupBy] group to its first row
34+ * or the first row matching a predicate, with handling [pivot] + [groupBy] combinations that contain no matching rows.
35+ */
1436class FirstTests : ColumnsSelectionDslTests () {
1537
38+ // region ColumnsSelectionDsl
39+
1640 @Test
1741 fun `ColumnsSelectionDsl first` () {
1842 shouldThrow<IllegalArgumentException > {
@@ -48,31 +72,39 @@ class FirstTests : ColumnsSelectionDslTests() {
4872 ).shouldAllBeEqual()
4973 }
5074
75+ // endregion
76+
77+ // region DataColumn
78+
5179 @Test
5280 fun `first on DataColumn` () {
5381 df.name.lastName.first() shouldBe " Cooper"
5482 df.age.first { it in 18 .. < 40 } shouldBe 20
55-
5683 shouldThrow<IndexOutOfBoundsException > {
5784 df.drop(df.nrow).isHappy.first()
5885 }
86+ shouldThrow<NoSuchElementException > {
87+ df.age.first { it > 50 }
88+ }
5989 }
6090
6191 @Test
6292 fun `firstOrNull on DataColumn` () {
6393 df.name.lastName.firstOrNull() shouldBe " Cooper"
6494 df.drop(2 ).weight.firstOrNull() shouldBe null
6595 df.drop(df.nrow).age.firstOrNull() shouldBe null
66-
6796 df.age.firstOrNull { it in 21 .. 30 } shouldBe 30
6897 df.age.firstOrNull { it > 50 } shouldBe null
6998 }
7099
100+ // endregion
101+
102+ // region DataFrame
103+
71104 @Test
72105 fun `first on DataFrame` () {
73106 df.first().name.lastName shouldBe " Cooper"
74107 df.first { ! isHappy }.name.lastName shouldBe " Daniels"
75-
76108 shouldThrow<NoSuchElementException > {
77109 df.drop(df.nrow).first()
78110 }
@@ -88,19 +120,22 @@ class FirstTests : ColumnsSelectionDslTests() {
88120 fun `firstOrNull on DataFrame` () {
89121 df.firstOrNull()?.name?.lastName shouldBe " Cooper"
90122 df.drop(df.nrow).firstOrNull() shouldBe null
91-
92123 df.firstOrNull { ! isHappy }?.name?.lastName shouldBe " Daniels"
93124 df.firstOrNull { age > 50 } shouldBe null
94125 df.drop(df.nrow).firstOrNull { isHappy } shouldBe null
95126 }
96127
128+ // endregion
129+
130+ // region GroupBy
131+
132+ // not tested on an empty dataframe because of #1531
97133 @Test
98134 fun `first on GroupBy` () {
99135 val grouped = df.groupBy { isHappy }
100136 val reducedGrouped = grouped.first()
101137 val firstHappy = reducedGrouped.values()[0 ]
102138 val firstUnhappy = reducedGrouped.values()[1 ]
103-
104139 firstHappy shouldBe dataFrameOf(
105140 " isHappy" to columnOf(true ),
106141 " name" to columnOf(
@@ -111,12 +146,11 @@ class FirstTests : ColumnsSelectionDslTests() {
111146 " city" to columnOf(" London" ),
112147 " weight" to columnOf(54 ),
113148 )[0 ]
114-
115149 firstUnhappy shouldBe dataFrameOf(
116150 " isHappy" to columnOf(false ),
117151 " name" to columnOf(
118152 " firstName" to columnOf(" Charlie" ),
119- " lastName" to columnOf(" Daniels" )
153+ " lastName" to columnOf(" Daniels" ),
120154 ),
121155 " age" to columnOf(20 ),
122156 " city" to columnOf(" Moscow" ),
@@ -127,10 +161,9 @@ class FirstTests : ColumnsSelectionDslTests() {
127161 @Test
128162 fun `first on GroupBy with predicate` () {
129163 val grouped = df.groupBy { isHappy }
130- val reducedGrouped = grouped.first{ it[" age" ] as Int > 17 && it[" city" ] != " Moscow" }
164+ val reducedGrouped = grouped.first { it[" age" ] as Int > 17 && it[" city" ] != " Moscow" }
131165 val firstHappy = reducedGrouped.values()[0 ]
132166 val firstUnhappy = reducedGrouped.values()[1 ]
133-
134167 firstHappy shouldBe dataFrameOf(
135168 " isHappy" to columnOf(true ),
136169 " name" to columnOf(
@@ -141,19 +174,50 @@ class FirstTests : ColumnsSelectionDslTests() {
141174 " city" to columnOf(" Dubai" ),
142175 " weight" to columnOf(87 ),
143176 )[0 ]
144-
145177 firstUnhappy shouldBe dataFrameOf(
146178 " isHappy" to columnOf(false ),
147179 " name" to columnOf(
148180 " firstName" to columnOf(" Alice" ),
149- " lastName" to columnOf(" Wolf" )
181+ " lastName" to columnOf(" Wolf" ),
150182 ),
151183 " age" to columnOf(20 ),
152184 " city" to columnOf(null ),
153185 " weight" to columnOf(55 ),
154186 )[0 ]
155187 }
156188
189+ @Test
190+ fun `first on GroupBy with predicate without match` () {
191+ val grouped = df.groupBy { isHappy }
192+ val reducedGrouped = grouped.first { it[" city" ] == " London" }
193+ val firstHappy = reducedGrouped.values()[0 ]
194+ val firstUnhappy = reducedGrouped.values()[1 ]
195+ firstHappy shouldBe dataFrameOf(
196+ " isHappy" to columnOf(true ),
197+ " name" to columnOf(
198+ " firstName" to columnOf(" Alice" ),
199+ " lastName" to columnOf(" Cooper" ),
200+ ),
201+ " age" to columnOf(15 ),
202+ " city" to columnOf(" London" ),
203+ " weight" to columnOf(54 ),
204+ )[0 ]
205+ firstUnhappy shouldBe dataFrameOf(
206+ " isHappy" to columnOf(false ),
207+ " name" to columnOf(
208+ " firstName" to columnOf(null ),
209+ " lastName" to columnOf(null ),
210+ ),
211+ " age" to columnOf(null ),
212+ " city" to columnOf(null ),
213+ " weight" to columnOf(null ),
214+ )[0 ]
215+ }
216+
217+ // endregion
218+
219+ // region Pivot
220+
157221 @Test
158222 fun `first on Pivot` () {
159223 val pivot = df.pivot { isHappy }
@@ -163,17 +227,16 @@ class FirstTests : ColumnsSelectionDslTests() {
163227 firstHappy shouldBe dataFrameOf(
164228 " name" to columnOf(
165229 " firstName" to columnOf(" Alice" ),
166- " lastName" to columnOf(" Cooper" )
230+ " lastName" to columnOf(" Cooper" ),
167231 ),
168232 " age" to columnOf(15 ),
169233 " city" to columnOf(" London" ),
170234 " weight" to columnOf(54 ),
171235 )[0 ]
172-
173236 firstUnhappy shouldBe dataFrameOf(
174237 " name" to columnOf(
175238 " firstName" to columnOf(" Charlie" ),
176- " lastName" to columnOf(" Daniels" )
239+ " lastName" to columnOf(" Daniels" ),
177240 ),
178241 " age" to columnOf(20 ),
179242 " city" to columnOf(" Moscow" ),
@@ -187,34 +250,59 @@ class FirstTests : ColumnsSelectionDslTests() {
187250 val reducedPivotAdults = pivot.first { age > 17 }
188251 val firstHappyAdult = reducedPivotAdults.values()[0 ]
189252 val firstUnhappyAdult = reducedPivotAdults.values()[1 ]
190-
191253 firstHappyAdult shouldBe dataFrameOf(
192254 " name" to columnOf(
193255 " firstName" to columnOf(" Bob" ),
194- " lastName" to columnOf(" Dylan" )
256+ " lastName" to columnOf(" Dylan" ),
195257 ),
196258 " age" to columnOf(45 ),
197259 " city" to columnOf(" Dubai" ),
198260 " weight" to columnOf(87 ),
199261 )[0 ]
200-
201262 firstUnhappyAdult shouldBe dataFrameOf(
202263 " name" to columnOf(
203264 " firstName" to columnOf(" Charlie" ),
204- " lastName" to columnOf(" Daniels" )
265+ " lastName" to columnOf(" Daniels" ),
205266 ),
206267 " age" to columnOf(20 ),
207268 " city" to columnOf(" Moscow" ),
208269 " weight" to columnOf(null ),
209270 )[0 ]
210271 }
211272
273+ @Test
274+ fun `first on Pivot with predicate without match` () {
275+ val pivot = df.pivot { isHappy }
276+ val reducedPivot = pivot.first { it[" city" ] == " London" }
277+ val firstHappy = reducedPivot.values()[0 ]
278+ val firstUnhappy = reducedPivot.values()[1 ]
279+ firstHappy shouldBe dataFrameOf(
280+ " name" to columnOf(
281+ " firstName" to columnOf(" Alice" ),
282+ " lastName" to columnOf(" Cooper" ),
283+ ),
284+ " age" to columnOf(15 ),
285+ " city" to columnOf(" London" ),
286+ " weight" to columnOf(54 ),
287+ )[0 ]
288+ firstUnhappy shouldBe dataFrameOf(
289+ " name" to columnOf(null ),
290+ " age" to columnOf(null ),
291+ " city" to columnOf(null ),
292+ " weight" to columnOf(null ),
293+ )[0 ]
294+ }
295+
296+ // endregion
297+
298+ // region PivotGroupBy
299+
212300 @Test
213301 fun `first on PivotGroupBy` () {
214302 val students = dataFrameOf(
215303 " name" to columnOf(" Alice" , " Alice" , " Alice" , " Alice" , " Bob" , " Bob" , " Bob" , " Bob" ),
216304 " age" to columnOf(15 , 15 , 20 , 20 , 15 , 15 , 20 , 20 ),
217- " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 )
305+ " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 ),
218306 )
219307 val studentsPivotGrouped = students.pivot(" age" ).groupBy(" name" )
220308 val studentsPivotGroupedReduced = studentsPivotGrouped.first().values()
@@ -223,7 +311,7 @@ class FirstTests : ColumnsSelectionDslTests() {
223311 " age" to columnOf(
224312 " 15" to columnOf(1 , 1 ),
225313 " 20" to columnOf(1 , 1 ),
226- )
314+ ),
227315 )
228316 studentsPivotGroupedReduced shouldBe expectedDf
229317 }
@@ -233,7 +321,7 @@ class FirstTests : ColumnsSelectionDslTests() {
233321 val students = dataFrameOf(
234322 " name" to columnOf(" Alice" , " Alice" , " Alice" , " Alice" , " Bob" , " Bob" , " Bob" , " Bob" ),
235323 " age" to columnOf(15 , 15 , 20 , 20 , 15 , 15 , 20 , 20 ),
236- " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 )
324+ " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 ),
237325 )
238326 val studentsPivotGrouped = students.pivot(" age" ).groupBy(" name" )
239327 val studentsPivotGroupedReduced = studentsPivotGrouped.first { it[" group" ] == 2 }.values()
@@ -242,7 +330,7 @@ class FirstTests : ColumnsSelectionDslTests() {
242330 " age" to columnOf(
243331 " 15" to columnOf(2 , 2 ),
244332 " 20" to columnOf(2 , 2 ),
245- )
333+ ),
246334 )
247335 studentsPivotGroupedReduced shouldBe expected
248336 }
@@ -252,7 +340,7 @@ class FirstTests : ColumnsSelectionDslTests() {
252340 val students = dataFrameOf(
253341 " name" to columnOf(" Alice" , " Alice" , " Alice" , " Alice" , " Bob" , " Bob" , " Bob" , " Bob" ),
254342 " age" to columnOf(15 , 15 , 20 , 20 , 15 , 15 , 20 , 20 ),
255- " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 )
343+ " group" to columnOf(1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 ),
256344 )
257345 val studentsPivotGrouped = students.pivot(" age" ).groupBy(" name" )
258346 val studentsPivotGroupedReduced = studentsPivotGrouped.first { it[" group" ] == 3 }.values()
@@ -261,8 +349,10 @@ class FirstTests : ColumnsSelectionDslTests() {
261349 " age" to columnOf(
262350 " 15" to columnOf(null , null ),
263351 " 20" to columnOf(null , null ),
264- )
352+ ),
265353 )
266354 studentsPivotGroupedReduced shouldBe expected
267355 }
356+
357+ // endregion
268358}
0 commit comments