PySpark-Column-Selectors 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PySparkSelectors/__init__.py +240 -0
- PySparkSelectors/__main__.py +4 -0
- PySparkSelectors/cli.py +18 -0
- PySparkSelectors/models.py +1564 -0
- PySparkSelectors/py.typed +1 -0
- PySparkSelectors/spark_overrides.py +677 -0
- PySparkSelectors/user_functions.py +745 -0
- PySparkSelectors/utils.py +298 -0
- pyspark_column_selectors-0.1.3.dist-info/METADATA +674 -0
- pyspark_column_selectors-0.1.3.dist-info/RECORD +13 -0
- pyspark_column_selectors-0.1.3.dist-info/WHEEL +4 -0
- pyspark_column_selectors-0.1.3.dist-info/entry_points.txt +2 -0
- pyspark_column_selectors-0.1.3.dist-info/licenses/LICENSE +279 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: PySpark_Column_Selectors
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Adds column selector functionality to pyspark
|
|
5
|
+
Project-URL: bugs, https://github.com/tmichel3796/PySparkSelectors/issues
|
|
6
|
+
Project-URL: changelog, https://github.com/tmichel3796/PySparkSelectors/releases
|
|
7
|
+
Project-URL: documentation, https://tmichel3796.github.io/PySparkSelectors/
|
|
8
|
+
Project-URL: homepage, https://github.com/tmichel3796/PySparkSelectors
|
|
9
|
+
Author-email: "Trevor A. Michel" <Info@TrevorMichel.com>
|
|
10
|
+
Maintainer-email: "Trevor A. Michel" <Info@TrevorMichel.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: pyspark
|
|
16
|
+
Requires-Dist: rich
|
|
17
|
+
Requires-Dist: typer
|
|
18
|
+
Requires-Dist: typing-extensions
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# PySpark Column Selectors
|
|
22
|
+
|
|
23
|
+
A small object-oriented framework for selecting DataFrame columns by **dtype**, **position**, or **name pattern** instead of typing out column names by hand. A selector can be passed almost anywhere a column or list of columns is normally accepted, and selectors can be combined with set operations and chained with ordinary column transforms.
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
Adds column selector functionality to pyspark
|
|
28
|
+
|
|
29
|
+
- GitHub: <https://github.com/tmichel3796/PySpark_Column_Selectors/>
|
|
30
|
+
- PyPI package: <https://pypi.org/project/PySpark_Column_Selectors/>
|
|
31
|
+
- Created by: [**Trevor A. Michel**](trevormichel.com) \| GitHub <https://github.com/tmichel3796> \| PyPI <https://pypi.org/user/tmichel3796/>
|
|
32
|
+
- Free software: MIT License
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
## Dependencies
|
|
37
|
+
|
|
38
|
+
- Python 3.8+
|
|
39
|
+
- PySpark 3.4 or newer (for full functionality)
|
|
40
|
+
- PySpark 3.3 still works, except `T.TimestampNTZType` won't exist
|
|
41
|
+
- `temporal()`/`datetime_()` fall back to matching only `T.TimestampType` on those older versions instead of raising.
|
|
42
|
+
- `withColumns` with multiple selector-driven mutations in a single call (see [Using selectors with other DataFrame operations](#using-selectors-with-other-dataframe-operations)) requires PySpark 3.3+, since `DataFrame.withColumns` itself didn't exist before then.
|
|
43
|
+
- No third-party packages beyond PySpark itself -- everything else used (`functools`, `operator`, `re`, `typing`) is part of the Python standard library.
|
|
44
|
+
- Spark Connect (`pyspark.sql.connect`) is supported automatically when present, but is not required -- this framework works the same either way.
|
|
45
|
+
|
|
46
|
+
## Importing
|
|
47
|
+
|
|
48
|
+
``` python
|
|
49
|
+
import PySparkSelectors as scs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every example below assumes this import, and calls each selector as `scs.<function_name>(...)`.
|
|
53
|
+
|
|
54
|
+
------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
## Example data
|
|
57
|
+
|
|
58
|
+
Every example in this document runs against the same small DataFrame:
|
|
59
|
+
|
|
60
|
+
``` python
|
|
61
|
+
df = spark.createDataFrame(
|
|
62
|
+
[
|
|
63
|
+
(1, "north", "east", 120.5, 88, True, "2024-01-15", 910, "2024-06-01 08:30:00"),
|
|
64
|
+
(2, "south", "west", 340.0, 95, False, "2024-03-22", 875, "2024-06-02 14:45:00"),
|
|
65
|
+
],
|
|
66
|
+
["id", "name", "region", "elevation", "totalscore", "isactive", "signupdate", "score2024", "lastseen"],
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| id | name | region | elevation | totalscore | isactive | signupdate | score2024 | lastseen |
|
|
71
|
+
|--------|--------|--------|--------|--------|--------|--------|--------|--------|
|
|
72
|
+
| 1 | north | east | 120.5 | 88 | true | 2024-01-15 | 910 | 2024-06-01 08:30:00 |
|
|
73
|
+
| 2 | south | west | 340.0 | 95 | false | 2024-03-22 | 875 | 2024-06-02 14:45:00 |
|
|
74
|
+
|
|
75
|
+
Dtypes: `id` (int), `name`/`region` (string), `elevation` (double), `totalscore`/`score2024` (int), `isactive` (boolean), `signupdate` (date), `lastseen` (timestamp).
|
|
76
|
+
|
|
77
|
+
------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
## Dtype-based selectors
|
|
80
|
+
|
|
81
|
+
Dtype selectors match against the real `pyspark.sql.types` classes, not dtype strings. Import the types module alongside the selectors:
|
|
82
|
+
|
|
83
|
+
``` python
|
|
84
|
+
import pyspark.sql.types as T
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `by_dtype([...])`
|
|
88
|
+
|
|
89
|
+
Matches any `pyspark.sql.types.DataType` class (or classes) you pass in. A category base class (e.g. `T.NumericType`) matches every concrete subclass of it too -- `by_dtype([T.NumericType])` matches integer, floating-point, *and* decimal columns in one call.
|
|
90
|
+
|
|
91
|
+
``` python
|
|
92
|
+
df.select(scs.by_dtype([T.StringType]))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| name | region |
|
|
96
|
+
|-------|--------|
|
|
97
|
+
| north | east |
|
|
98
|
+
| south | west |
|
|
99
|
+
|
|
100
|
+
### `string()`
|
|
101
|
+
|
|
102
|
+
``` python
|
|
103
|
+
df.select(scs.string())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| name | region |
|
|
107
|
+
|-------|--------|
|
|
108
|
+
| north | east |
|
|
109
|
+
| south | west |
|
|
110
|
+
|
|
111
|
+
### `boolean()`
|
|
112
|
+
|
|
113
|
+
``` python
|
|
114
|
+
df.select(scs.boolean())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| isactive |
|
|
118
|
+
|----------|
|
|
119
|
+
| true |
|
|
120
|
+
| false |
|
|
121
|
+
|
|
122
|
+
### `binary()`
|
|
123
|
+
|
|
124
|
+
There are no binary columns in this DataFrame, so this raises an error by default rather than returning nothing -- see [Handling zero matches](#handling-zero-matches) below.
|
|
125
|
+
|
|
126
|
+
### `integer()`
|
|
127
|
+
|
|
128
|
+
Fixed-width integer columns (tinyint/smallint/int/bigint).
|
|
129
|
+
|
|
130
|
+
``` python
|
|
131
|
+
df.select(scs.integer())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| id | totalscore | score2024 |
|
|
135
|
+
|-----|------------|-----------|
|
|
136
|
+
| 1 | 88 | 910 |
|
|
137
|
+
| 2 | 95 | 875 |
|
|
138
|
+
|
|
139
|
+
### `floats()`
|
|
140
|
+
|
|
141
|
+
Floating-point columns (float/double).
|
|
142
|
+
|
|
143
|
+
``` python
|
|
144
|
+
df.select(scs.floats())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| elevation |
|
|
148
|
+
|-----------|
|
|
149
|
+
| 120.5 |
|
|
150
|
+
| 340.0 |
|
|
151
|
+
|
|
152
|
+
### `numeric()`
|
|
153
|
+
|
|
154
|
+
Every fixed-width numeric column (integer or floating-point).
|
|
155
|
+
|
|
156
|
+
``` python
|
|
157
|
+
df.select(scs.numeric())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| id | elevation | totalscore | score2024 |
|
|
161
|
+
|-----|-----------|------------|-----------|
|
|
162
|
+
| 1 | 120.5 | 88 | 910 |
|
|
163
|
+
| 2 | 340.0 | 95 | 875 |
|
|
164
|
+
|
|
165
|
+
### `date()`
|
|
166
|
+
|
|
167
|
+
``` python
|
|
168
|
+
df.select(scs.date())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| signupdate |
|
|
172
|
+
|------------|
|
|
173
|
+
| 2024-01-15 |
|
|
174
|
+
| 2024-03-22 |
|
|
175
|
+
|
|
176
|
+
### `datetime_()`
|
|
177
|
+
|
|
178
|
+
``` python
|
|
179
|
+
df.select(scs.datetime_())
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| lastseen |
|
|
183
|
+
|---------------------|
|
|
184
|
+
| 2024-06-01 08:30:00 |
|
|
185
|
+
| 2024-06-02 14:45:00 |
|
|
186
|
+
|
|
187
|
+
### `temporal()`
|
|
188
|
+
|
|
189
|
+
Date or timestamp columns.
|
|
190
|
+
|
|
191
|
+
``` python
|
|
192
|
+
df.select(scs.temporal())
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| signupdate | lastseen |
|
|
196
|
+
|------------|---------------------|
|
|
197
|
+
| 2024-01-15 | 2024-06-01 08:30:00 |
|
|
198
|
+
| 2024-03-22 | 2024-06-02 14:45:00 |
|
|
199
|
+
|
|
200
|
+
------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
## Position-based selectors
|
|
203
|
+
|
|
204
|
+
### `by_index(*indexes)`
|
|
205
|
+
|
|
206
|
+
Selects one or more columns by position. Negative positions work the same way Python indexing does (`-1` is the last column), and a `range(...)` can be mixed in with plain integers to grab a span in one call.
|
|
207
|
+
|
|
208
|
+
``` python
|
|
209
|
+
df.select(scs.by_index(range(0, 1), 3))
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| id | name | elevation |
|
|
213
|
+
|-----|-------|-----------|
|
|
214
|
+
| 1 | north | 120.5 |
|
|
215
|
+
| 2 | south | 340.0 |
|
|
216
|
+
|
|
217
|
+
### `first()`
|
|
218
|
+
|
|
219
|
+
``` python
|
|
220
|
+
df.select(scs.first())
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| id |
|
|
224
|
+
|-----|
|
|
225
|
+
| 1 |
|
|
226
|
+
| 2 |
|
|
227
|
+
|
|
228
|
+
### `last()`
|
|
229
|
+
|
|
230
|
+
``` python
|
|
231
|
+
df.select(scs.last())
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
| lastseen |
|
|
235
|
+
|---------------------|
|
|
236
|
+
| 2024-06-01 08:30:00 |
|
|
237
|
+
| 2024-06-02 14:45:00 |
|
|
238
|
+
|
|
239
|
+
### Handle column not in range error {#handle-column-not-in-range-error}
|
|
240
|
+
|
|
241
|
+
By default, `by_index` raises if any requested position is out of range for the DataFrame (this DataFrame has 9 columns, at positions 0-8):
|
|
242
|
+
|
|
243
|
+
``` python
|
|
244
|
+
df.select(scs.by_index(9))
|
|
245
|
+
# raises pyspark.errors.PySparkValueError -- position 9 is out of range
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Passing `strict_index_bounds=False` skips any out-of-range position instead of raising, so only the in-range positions are matched:
|
|
249
|
+
|
|
250
|
+
``` python
|
|
251
|
+
df.select(scs.by_index(0, 9, strict_index_bounds=False))
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
| id |
|
|
255
|
+
|-----|
|
|
256
|
+
| 1 |
|
|
257
|
+
| 2 |
|
|
258
|
+
|
|
259
|
+
> Note: if every requested position ends up out of range, `strict_index_bounds=False` alone still isn't enough to avoid an error -- the selector would then match zero columns, which raises on its own (see [Handling zero matches](#handling-zero-matches)). To allow that case too, also pass `require_col_match=False`.
|
|
260
|
+
|
|
261
|
+
------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
## Name-based selectors
|
|
264
|
+
|
|
265
|
+
All name-based matching below is case-insensitive -- `scs.by_name("REGION")` matches a column named `region` just as well as `scs.by_name("region")` does, and column names may contain a literal dot (e.g. `meta.source`) without being mistaken for a nested field.
|
|
266
|
+
|
|
267
|
+
### `matches(pattern)`
|
|
268
|
+
|
|
269
|
+
Selects columns whose name matches a regular expression.
|
|
270
|
+
|
|
271
|
+
``` python
|
|
272
|
+
df.select(scs.matches("score"))
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
| totalscore | score2024 |
|
|
276
|
+
|------------|-----------|
|
|
277
|
+
| 88 | 910 |
|
|
278
|
+
| 95 | 875 |
|
|
279
|
+
|
|
280
|
+
`matches` takes any pattern the `re` module can compile, not just a plain substring -- anchors, character classes, and alternation all work:
|
|
281
|
+
|
|
282
|
+
``` python
|
|
283
|
+
df.select(scs.matches(r"^s.*[aeiou]$|\d"))
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
| signupdate | score2024 |
|
|
287
|
+
|------------|-----------|
|
|
288
|
+
| 2024-01-15 | 910 |
|
|
289
|
+
| 2024-03-22 | 875 |
|
|
290
|
+
|
|
291
|
+
This pattern matches a column name if it either starts with `s` and ends in a vowel (`signupdate`), or contains a digit anywhere in the name (`score2024`).
|
|
292
|
+
|
|
293
|
+
### `starts_with(*prefixes)`
|
|
294
|
+
|
|
295
|
+
``` python
|
|
296
|
+
df.select(scs.starts_with("e"))
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| elevation |
|
|
300
|
+
|-----------|
|
|
301
|
+
| 120.5 |
|
|
302
|
+
| 340.0 |
|
|
303
|
+
|
|
304
|
+
### `ends_with(*suffixes)`
|
|
305
|
+
|
|
306
|
+
``` python
|
|
307
|
+
df.select(scs.ends_with("date"))
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
| signupdate |
|
|
311
|
+
|------------|
|
|
312
|
+
| 2024-01-15 |
|
|
313
|
+
| 2024-03-22 |
|
|
314
|
+
|
|
315
|
+
### `contains(*substrings)`
|
|
316
|
+
|
|
317
|
+
``` python
|
|
318
|
+
df.select(scs.contains("2024"))
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
| score2024 |
|
|
322
|
+
|-----------|
|
|
323
|
+
| 910 |
|
|
324
|
+
| 875 |
|
|
325
|
+
|
|
326
|
+
### `by_name(*names)`
|
|
327
|
+
|
|
328
|
+
Selects an exact list of column names.
|
|
329
|
+
|
|
330
|
+
``` python
|
|
331
|
+
df.select(scs.by_name("id", "region"))
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
| id | region |
|
|
335
|
+
|-----|--------|
|
|
336
|
+
| 1 | east |
|
|
337
|
+
| 2 | west |
|
|
338
|
+
|
|
339
|
+
### `exclude(*names)`
|
|
340
|
+
|
|
341
|
+
Selects every column except the given names.
|
|
342
|
+
|
|
343
|
+
``` python
|
|
344
|
+
df.select(scs.exclude("region"))
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
| id | name | elevation | totalscore | isactive | signupdate | score2024 | lastseen |
|
|
348
|
+
|---------|---------|---------|---------|---------|---------|---------|---------|
|
|
349
|
+
| 1 | north | 120.5 | 88 | true | 2024-01-15 | 910 | 2024-06-01 08:30:00 |
|
|
350
|
+
| 2 | south | 340.0 | 95 | false | 2024-03-22 | 875 | 2024-06-02 14:45:00 |
|
|
351
|
+
|
|
352
|
+
### `alpha()`
|
|
353
|
+
|
|
354
|
+
Column names made up only of letters. `score2024` is the only column name containing a digit, so it's the only one excluded here.
|
|
355
|
+
|
|
356
|
+
``` python
|
|
357
|
+
df.select(scs.alpha())
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
| id | name | region | elevation | totalscore | isactive | signupdate | lastseen |
|
|
361
|
+
|---------|---------|---------|---------|---------|---------|---------|---------|
|
|
362
|
+
| 1 | north | east | 120.5 | 88 | true | 2024-01-15 | 2024-06-01 08:30:00 |
|
|
363
|
+
| 2 | south | west | 340.0 | 95 | false | 2024-03-22 | 2024-06-02 14:45:00 |
|
|
364
|
+
|
|
365
|
+
### `alphanumeric()`
|
|
366
|
+
|
|
367
|
+
Column names made up only of letters and/or digits -- every column name in this DataFrame qualifies, including `score2024`.
|
|
368
|
+
|
|
369
|
+
``` python
|
|
370
|
+
df.select(scs.alphanumeric())
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Returns every column, identical to the example data table above.
|
|
374
|
+
|
|
375
|
+
### `all()`
|
|
376
|
+
|
|
377
|
+
``` python
|
|
378
|
+
df.select(scs.all())
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Returns every column, identical to the example data table above.
|
|
382
|
+
|
|
383
|
+
------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
## Combining selectors
|
|
386
|
+
|
|
387
|
+
Selectors support set-style operators so more specific selections can be built without listing columns by hand. Combined selectors always resolve columns in the same order they appear in the underlying DataFrame.
|
|
388
|
+
|
|
389
|
+
### Union (`|`)
|
|
390
|
+
|
|
391
|
+
``` python
|
|
392
|
+
df.select(scs.string() | scs.starts_with("e"))
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
| name | region | elevation |
|
|
396
|
+
|-------|--------|-----------|
|
|
397
|
+
| north | east | 120.5 |
|
|
398
|
+
| south | west | 340.0 |
|
|
399
|
+
|
|
400
|
+
### Intersection (`&`)
|
|
401
|
+
|
|
402
|
+
``` python
|
|
403
|
+
df.select(scs.numeric() & scs.matches("score"))
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
| totalscore | score2024 |
|
|
407
|
+
|------------|-----------|
|
|
408
|
+
| 88 | 910 |
|
|
409
|
+
| 95 | 875 |
|
|
410
|
+
|
|
411
|
+
### Difference (`-`)
|
|
412
|
+
|
|
413
|
+
``` python
|
|
414
|
+
df.select(scs.all() - scs.numeric())
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
| name | region | isactive | signupdate | lastseen |
|
|
418
|
+
|-------|--------|----------|------------|---------------------|
|
|
419
|
+
| north | east | true | 2024-01-15 | 2024-06-01 08:30:00 |
|
|
420
|
+
| south | west | false | 2024-03-22 | 2024-06-02 14:45:00 |
|
|
421
|
+
|
|
422
|
+
### Symmetric difference (`^`)
|
|
423
|
+
|
|
424
|
+
Columns matched by exactly one side, not both.
|
|
425
|
+
|
|
426
|
+
``` python
|
|
427
|
+
df.select(scs.contains("score") ^ scs.numeric())
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
| id | elevation |
|
|
431
|
+
|-----|-----------|
|
|
432
|
+
| 1 | 120.5 |
|
|
433
|
+
| 2 | 340.0 |
|
|
434
|
+
|
|
435
|
+
### Complement (`~`)
|
|
436
|
+
|
|
437
|
+
``` python
|
|
438
|
+
df.select(~scs.temporal())
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
| id | name | region | elevation | totalscore | isactive | score2024 |
|
|
442
|
+
|-----|-------|--------|-----------|------------|----------|-----------|
|
|
443
|
+
| 1 | north | east | 120.5 | 88 | true | 910 |
|
|
444
|
+
| 2 | south | west | 340.0 | 95 | false | 875 |
|
|
445
|
+
|
|
446
|
+
------------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
## Chaining transforms onto a selector
|
|
449
|
+
|
|
450
|
+
A transform can be chained onto a selector before it's resolved. The transform is applied to every column the selector matches, and the original column name is preserved in the result.
|
|
451
|
+
|
|
452
|
+
### `.upper()`
|
|
453
|
+
|
|
454
|
+
``` python
|
|
455
|
+
df.select(scs.string().upper())
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
| name | region |
|
|
459
|
+
|-------|--------|
|
|
460
|
+
| NORTH | EAST |
|
|
461
|
+
| SOUTH | WEST |
|
|
462
|
+
|
|
463
|
+
### `.cast(...)`
|
|
464
|
+
|
|
465
|
+
``` python
|
|
466
|
+
df.select(scs.floats().cast("string"))
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
| elevation |
|
|
470
|
+
|-----------|
|
|
471
|
+
| 120.5 |
|
|
472
|
+
| 340.0 |
|
|
473
|
+
|
|
474
|
+
The values look the same, but `elevation` is now a `string` column instead of a `double` column.
|
|
475
|
+
|
|
476
|
+
### Arithmetic
|
|
477
|
+
|
|
478
|
+
``` python
|
|
479
|
+
df.select(scs.integer() + 1)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
| id | totalscore | score2024 |
|
|
483
|
+
|-----|------------|-----------|
|
|
484
|
+
| 2 | 89 | 911 |
|
|
485
|
+
| 3 | 96 | 876 |
|
|
486
|
+
|
|
487
|
+
Because every column function available in PySpark is also available as a chained method on a selector, this works with things like `.substr(...)`, `.isNull()`, `.between(...)`, and comparison operators too, not just a fixed list of built-ins.
|
|
488
|
+
|
|
489
|
+
### Renaming columns:
|
|
490
|
+
|
|
491
|
+
`.prefix(...)`, `.suffix(...)`, and `.map_alias(...)` Three functions used to modify a columns name.
|
|
492
|
+
|
|
493
|
+
#### `.prefix(prefix)`
|
|
494
|
+
|
|
495
|
+
Prepends `prefix` to every matched column's name.
|
|
496
|
+
|
|
497
|
+
``` python
|
|
498
|
+
df.select(scs.by_name("id", "region").prefix("src_"))
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
| src_id | src_region |
|
|
502
|
+
|--------|------------|
|
|
503
|
+
| 1 | east |
|
|
504
|
+
| 2 | west |
|
|
505
|
+
|
|
506
|
+
#### `.suffix(suffix)`
|
|
507
|
+
|
|
508
|
+
Appends `suffix` to every matched column's name.
|
|
509
|
+
|
|
510
|
+
``` python
|
|
511
|
+
df.select(scs.numeric().suffix("_raw"))
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
| id_raw | elevation_raw | totalscore_raw | score2024_raw |
|
|
515
|
+
|--------|---------------|----------------|---------------|
|
|
516
|
+
| 1 | 120.5 | 88 | 910 |
|
|
517
|
+
| 2 | 340.0 | 95 | 875 |
|
|
518
|
+
|
|
519
|
+
#### `.map_alias(func)`
|
|
520
|
+
|
|
521
|
+
Renames every matched column by passing its current name to `func`, a one-argument function returning the new name. `.prefix(...)` and `.suffix(...)` are both just `.map_alias(...)` with a prepend/append built in -- `.map_alias(...)` is for anything more custom.
|
|
522
|
+
|
|
523
|
+
``` python
|
|
524
|
+
df.select(scs.by_name("name", "region").map_alias(lambda n: n.upper()))
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
| NAME | REGION |
|
|
528
|
+
|-------|--------|
|
|
529
|
+
| north | east |
|
|
530
|
+
| south | west |
|
|
531
|
+
|
|
532
|
+
Note this renames the *columns*, not the *values* -- compare to `.upper()` above, which uppercases the values but keeps the column names unchanged.
|
|
533
|
+
|
|
534
|
+
------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
## Using selectors with row filtering
|
|
537
|
+
|
|
538
|
+
A selector used inside a comparison becomes a row condition instead of a column pick, and conditions built this way can be combined with `&`, `|`, and `~` the same way plain columns can. `filter`/`where` still return every original column for the matching rows -- only the relevant column(s) are shown below for readability.
|
|
539
|
+
|
|
540
|
+
### A single condition
|
|
541
|
+
|
|
542
|
+
``` python
|
|
543
|
+
df.filter(scs.by_name("totalscore") > 90)
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
| id | totalscore |
|
|
547
|
+
|-----|------------|
|
|
548
|
+
| 2 | 95 |
|
|
549
|
+
|
|
550
|
+
*(the row for `id=1` is dropped, since `totalscore` there is 88)*
|
|
551
|
+
|
|
552
|
+
### Combining conditions with `&`
|
|
553
|
+
|
|
554
|
+
``` python
|
|
555
|
+
df.filter((scs.by_name("totalscore") > 90) & (scs.by_name("elevation") > 200))
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
| id | totalscore | elevation |
|
|
559
|
+
|-----|------------|-----------|
|
|
560
|
+
| 2 | 95 | 340.0 |
|
|
561
|
+
|
|
562
|
+
------------------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
## Using selectors with other DataFrame operations {#using-selectors-with-other-dataframe-operations}
|
|
565
|
+
|
|
566
|
+
### `select` with multiple selectors
|
|
567
|
+
|
|
568
|
+
``` python
|
|
569
|
+
df.select(scs.string(), scs.integer())
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
| name | region | id | totalscore | score2024 |
|
|
573
|
+
|-------|--------|-----|------------|-----------|
|
|
574
|
+
| north | east | 1 | 88 | 910 |
|
|
575
|
+
| south | west | 2 | 95 | 875 |
|
|
576
|
+
|
|
577
|
+
### `groupBy` / `agg`
|
|
578
|
+
|
|
579
|
+
``` python
|
|
580
|
+
df.groupBy(scs.by_name("region")).agg(scs.integer().sum())
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
| region | sum(id) | sum(totalscore) | sum(score2024) |
|
|
584
|
+
|--------|---------|-----------------|----------------|
|
|
585
|
+
| east | 1 | 88 | 910 |
|
|
586
|
+
| west | 2 | 95 | 875 |
|
|
587
|
+
|
|
588
|
+
### `drop`
|
|
589
|
+
|
|
590
|
+
``` python
|
|
591
|
+
df.drop(scs.by_name("lastseen", "signupdate"))
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Removes `lastseen` and `signupdate`, leaving `id`, `name`, `region`, `elevation`, `totalscore`, `isactive`, `score2024`.
|
|
595
|
+
|
|
596
|
+
### `sort` / `orderBy`
|
|
597
|
+
|
|
598
|
+
``` python
|
|
599
|
+
df.sort(scs.by_name("elevation").desc())
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
| id | elevation |
|
|
603
|
+
|-----|-----------|
|
|
604
|
+
| 2 | 340.0 |
|
|
605
|
+
| 1 | 120.5 |
|
|
606
|
+
|
|
607
|
+
*(every other original column is still present in the result; only `elevation` is shown here for readability)*
|
|
608
|
+
|
|
609
|
+
### `withColumns` with multiple selector-driven mutations
|
|
610
|
+
|
|
611
|
+
``` python
|
|
612
|
+
df.withColumns(
|
|
613
|
+
scs.floats().cast("string"),
|
|
614
|
+
scs.string().upper(),
|
|
615
|
+
)
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
| id | name | region | elevation |
|
|
619
|
+
|-----|-------|--------|-----------|
|
|
620
|
+
| 1 | NORTH | EAST | 120.5 |
|
|
621
|
+
| 2 | SOUTH | WEST | 340.0 |
|
|
622
|
+
|
|
623
|
+
Both mutations happen in the same call: every double column is cast to string, and every string column is uppercased.
|
|
624
|
+
|
|
625
|
+
------------------------------------------------------------------------
|
|
626
|
+
|
|
627
|
+
## Checking whether something is a selector
|
|
628
|
+
|
|
629
|
+
``` python
|
|
630
|
+
scs.is_selector(scs.numeric()) # True
|
|
631
|
+
scs.is_selector("totalscore") # False
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
------------------------------------------------------------------------
|
|
635
|
+
|
|
636
|
+
## Handling zero matches {#handling-zero-matches}
|
|
637
|
+
|
|
638
|
+
By default, if a selector resolves to zero columns against a given DataFrame, it raises an error rather than silently returning nothing. This also applies to combined selectors -- for example, subtracting a selector that covers every matching column from itself will raise, since the result has nothing left in it.
|
|
639
|
+
|
|
640
|
+
``` python
|
|
641
|
+
df.select(scs.binary())
|
|
642
|
+
# raises pyspark.errors.PySparkValueError -- zero columns matched
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Every selector function accepts a `require_col_match` keyword argument, which defaults to `True`. Passing `require_col_match=False` opts out of this check for that particular selector, allowing a zero-column match to pass through silently instead of raising:
|
|
646
|
+
|
|
647
|
+
``` python
|
|
648
|
+
df.select(scs.binary(require_col_match=False))
|
|
649
|
+
# returns a result with zero columns instead of raising
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
This also applies to `by_dtype`, `by_index`, and `matches` directly:
|
|
653
|
+
|
|
654
|
+
``` python
|
|
655
|
+
df.select(scs.all() - scs.by_index(range(0, 8)))
|
|
656
|
+
# There are 9 total columns in the df.
|
|
657
|
+
# As such the above column selector expression returns all columns - all columns.
|
|
658
|
+
# as such no columns of data are returned.
|
|
659
|
+
# Result: raises pyspark.errors.PySparkValueError -- zero columns matched
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
To bypass the no columns returned error, see the below examples. All column selector functions have access to require_col_match by_index is the only one that offers access to strict_index_bounds as shown in [Handle column not in range error](#handle-column-not-in-range-error)
|
|
663
|
+
|
|
664
|
+
``` python
|
|
665
|
+
df.select(scs.by_dtype([T.BinaryType], require_col_match=False))
|
|
666
|
+
df.select(scs.by_index(9, strict_index_bounds=False, require_col_match=False))
|
|
667
|
+
df.select(scs.matches("no_such_pattern", require_col_match=False))
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Author
|
|
671
|
+
|
|
672
|
+
PySparkSelectors was created in 2026 by Trevor A. Michel.
|
|
673
|
+
|
|
674
|
+
Built with [Cookiecutter](https://github.com/cookiecutter/cookiecutter) and the [audreyfeldroy/cookiecutter-pypackage](https://github.com/audreyfeldroy/cookiecutter-pypackage) project template.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
PySparkSelectors/__init__.py,sha256=E22iHgXsvwBoswHdxVhsBqPHjmiHfgOHn9aGHZjZofk,7196
|
|
2
|
+
PySparkSelectors/__main__.py,sha256=Qd-f8z2Q2vpiEP2x6PBFsJrpACWDVxFKQk820MhFmHo,59
|
|
3
|
+
PySparkSelectors/cli.py,sha256=HbigdTpEa4nVpBOmv3Yo5TvzEufBb0RU8jNCxike3OA,424
|
|
4
|
+
PySparkSelectors/models.py,sha256=wlotfpDGXK2yuMiBn8_JhGkhYoYyHNEYS_T5f_psBs0,60688
|
|
5
|
+
PySparkSelectors/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
|
|
6
|
+
PySparkSelectors/spark_overrides.py,sha256=SzlKR4ZneHzI-41MqxdiGe4y8w4tdBhy4KKLmXBD3lM,27100
|
|
7
|
+
PySparkSelectors/user_functions.py,sha256=d5EjOmXUQ6bkpgJNCWnU3lvjw9OALyt3b_0k5k3ppPA,24535
|
|
8
|
+
PySparkSelectors/utils.py,sha256=1SAf2SUXuLxIYMxF7qnm2AHt3ALK-SDvsUNn8sVh0n8,11133
|
|
9
|
+
pyspark_column_selectors-0.1.3.dist-info/METADATA,sha256=15WBT99qqy08od1DMuSBFR-nwfDv0swvMb6Rf4tYV-U,19572
|
|
10
|
+
pyspark_column_selectors-0.1.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
11
|
+
pyspark_column_selectors-0.1.3.dist-info/entry_points.txt,sha256=lDrzJJqOS4wQeRcg4ImS80KEIgogVbR_xtLMHpL0n7U,62
|
|
12
|
+
pyspark_column_selectors-0.1.3.dist-info/licenses/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
|
13
|
+
pyspark_column_selectors-0.1.3.dist-info/RECORD,,
|