Skill Tracer
Skills › Scientific Agent Skills › vaex › data_processing.md

data_processing.md

154 lines · skills/vaex/references/data_processing.md at 5f2bc03

Open on GitHub
1# Processing, statistics and joins
2
3Targets core 4.19.0. Sources: [API](https://vaex.io/docs/api.html),
4[missing-data guide](https://vaex.io/docs/guides/missing_or_invalid_data.html), and released
5`expression.py`, `functions.py`, `agg.py`, `groupby.py`, and `join.py`.
6

Processing, statistics and joins

Targets core 4.19.0. Sources: API, missing-data guide, and released expression.py, functions.py, agg.py, groupby.py, and join.py.

Raw lines 1 to 6
7## Filters, selections and missing values
8
9```python
10import numpy as np
11import pyarrow as pa
12import vaex
14df = vaex.from_arrays(
15 x=np.array([1., 2., 3., 4., 5., 6.]),
16 y=np.array([1., 4., 9., 16., 25., 36.]),
17 group=np.array(['A', 'B', 'A', 'B', 'A', 'B']),
18 value=pa.array([1., None, float('nan'), 4., 5., 6.]),
19 text=np.array([' Alice ', 'Bob', 'Cara', 'Dan', 'Eve', 'Frank']),
20 timestamp=np.array(['2024-01-01', '2024-01-02', '2024-02-01',
21 '2024-02-02', '2024-03-01', '2024-03-02'], dtype='datetime64[ns]'),
23filtered = df[(df.x >= 2) & (df.x < 5)]
24assert filtered.x.tolist() == [2., 3., 4.]
25df.select(df.x >= 4, name='high')
26assert df.count(selection='high') == 3
27assert df.mean('x', selection='high') == 5
28assert df[df.group.isin(['A'])].count() == 3
29assert df.value.isna().sum() == 2 # null/masked or NaN
30assert df.value.ismissing().sum() == 1 # null/masked
31assert df.value.isnan().sum() == 1 # IEEE NaN
32df['filled'] = df.value.fillna(0)
33assert df.filled.tolist() == [1., 0., 0., 4., 5., 6.]
34```
36Named selections affect operations using `selection=...`; creating one does not
37remove rows from the frame. Boolean expressions need `&`, `|`, `~` and parentheses.
38Use explicit inequalities for intervals; there is no `Expression.between` method.
39`fillna(value)` does not accept pandas `method='ffill'`. Forward/backward filling
40requires explicit ordering, group boundaries and time-gap policy outside that API.
41Never silently impute scientific measurements to zero; the value above is a mechanics example.

Filters, selections and missing values

import numpy as np
import pyarrow as pa
import vaex

df = vaex.from_arrays(
    x=np.array([1., 2., 3., 4., 5., 6.]),
    y=np.array([1., 4., 9., 16., 25., 36.]),
    group=np.array(['A', 'B', 'A', 'B', 'A', 'B']),
    value=pa.array([1., None, float('nan'), 4., 5., 6.]),
    text=np.array([' Alice ', 'Bob', 'Cara', 'Dan', 'Eve', 'Frank']),
    timestamp=np.array(['2024-01-01', '2024-01-02', '2024-02-01',
                        '2024-02-02', '2024-03-01', '2024-03-02'], dtype='datetime64[ns]'),
)
filtered = df[(df.x >= 2) & (df.x < 5)]
assert filtered.x.tolist() == [2., 3., 4.]
df.select(df.x >= 4, name='high')
assert df.count(selection='high') == 3
assert df.mean('x', selection='high') == 5
assert df[df.group.isin(['A'])].count() == 3
assert df.value.isna().sum() == 2       # null/masked or NaN
assert df.value.ismissing().sum() == 1  # null/masked
assert df.value.isnan().sum() == 1      # IEEE NaN
df['filled'] = df.value.fillna(0)
assert df.filled.tolist() == [1., 0., 0., 4., 5., 6.]

Named selections affect operations using selection=...; creating one does not remove rows from the frame. Boolean expressions need &, |, ~ and parentheses. Use explicit inequalities for intervals; there is no Expression.between method. fillna(value) does not accept pandas method='ffill'. Forward/backward filling requires explicit ordering, group boundaries and time-gap policy outside that API. Never silently impute scientific measurements to zero; the value above is a mechanics example.

Raw lines 7 to 42
43## Virtual numeric, text and date transformations
45```python
46df['squared'] = df.x ** 2
47df['log_x'] = df.x.log() # x is strictly positive in this fixture
48df['radius'] = (df.x ** 2 + df.y ** 2).sqrt()
49df['band'] = (df.x >= 4).where('high', 'low')
50df['clean_name'] = df.text.str.strip().str.lower()
51df['starts_a'] = df.clean_name.str.startswith('a')
52df['padded'] = df.group.str.pad(width=4, side='left', fillchar='0')
53df['year'] = df.timestamp.dt.year
54df['month'] = df.timestamp.dt.month
55df['weekday'] = df.timestamp.dt.dayofweek
56df['next_day'] = df.timestamp + np.timedelta64(1, 'D')
57df['bin'] = df.x.digitize([2., 4., 6.])
58assert df.bin.tolist() == [0, 1, 1, 2, 2, 3]
59assert df.band.tolist() == ['low'] * 3 + ['high'] * 3
60assert df.clean_name.tolist()[0] == 'alice'
61assert df.month.tolist() == [1, 1, 2, 2, 3, 3]
62```
64`str.replace(pat, repl, regex=False)` defaults to literal replacement. `str.split`
65returns list-valued rows; indexing the resulting expression with `[0]` selects a row,
66not the first token in every row. Do not treat it as pandas `.str` list indexing.
67For token extraction prefer a verified regex/extraction path or a bounded Arrow operation.
68Datetime casting is not a general free-form parser: normalize format/time zone first,
69preserve the original field, and verify timezone/DST and invalid-date handling.
70`digitize` returns bin indices including underflow/overflow; define labels and edges
71explicitly instead of discarding these rows. `vaex.vrange` generates virtual numeric
72values; it is not a groupby binning instruction.

Virtual numeric, text and date transformations

df['squared'] = df.x ** 2
df['log_x'] = df.x.log()  # x is strictly positive in this fixture
df['radius'] = (df.x ** 2 + df.y ** 2).sqrt()
df['band'] = (df.x >= 4).where('high', 'low')
df['clean_name'] = df.text.str.strip().str.lower()
df['starts_a'] = df.clean_name.str.startswith('a')
df['padded'] = df.group.str.pad(width=4, side='left', fillchar='0')
df['year'] = df.timestamp.dt.year
df['month'] = df.timestamp.dt.month
df['weekday'] = df.timestamp.dt.dayofweek
df['next_day'] = df.timestamp + np.timedelta64(1, 'D')
df['bin'] = df.x.digitize([2., 4., 6.])
assert df.bin.tolist() == [0, 1, 1, 2, 2, 3]
assert df.band.tolist() == ['low'] * 3 + ['high'] * 3
assert df.clean_name.tolist()[0] == 'alice'
assert df.month.tolist() == [1, 1, 2, 2, 3, 3]

str.replace(pat, repl, regex=False) defaults to literal replacement. str.split returns list-valued rows; indexing the resulting expression with [0] selects a row, not the first token in every row. Do not treat it as pandas .str list indexing. For token extraction prefer a verified regex/extraction path or a bounded Arrow operation. Datetime casting is not a general free-form parser: normalize format/time zone first, preserve the original field, and verify timezone/DST and invalid-date handling. digitize returns bin indices including underflow/overflow; define labels and edges explicitly instead of discarding these rows. vaex.vrange generates virtual numeric values; it is not a groupby binning instruction.

Raw lines 43 to 73
74## Reductions and approximate percentiles
76```python
77assert df.x.sum() == 21
78assert np.isclose(df.x.var(), np.var(np.arange(1., 7.), ddof=0))
79assert np.isclose(df.x.std(), np.std(np.arange(1., 7.), ddof=0))
80assert df.count() == 6
81assert df.value.count() == 4
82assert df.x.minmax().tolist() == [1., 6.]
83assert df.group.nunique() == 2
84correlation = df.correlation('x', 'y')
85assert 0.9 < correlation <= 1
86# Histogram-based approximation, not NumPy's exact linear-interpolation quantile:
87quartiles = df.percentile_approx('x', percentage=[25, 50, 75], percentile_shape=4096)
88assert np.all(np.diff(quartiles) >= 0)
89assert 1 <= quartiles[0] <= quartiles[-1] <= 6
90pending = [df.x.mean(delay=True), df.y.sum(delay=True)]
91df.execute()
92assert [p.get().item() for p in pending] == [3.5, 91.0]
93```
95`std`/`var` use the population denominator here (ddof=0), despite an imprecise
96"sample variance" docstring. For an unbiased sample variance, compute the valid
97count and multiply population variance by `n/(n-1)` only when `n>1`; this does not
98address weights, clustering or dependence. NaNs/nulls are skipped by many reductions;
99report the valid count for the exact expression and selection. Infinity is a separate
100case. `unique()` and high-cardinality groups allocate outputs proportional to cardinality.
101There is no `.quantile()` or `.prod()` expression method in this release.

Reductions and approximate percentiles

assert df.x.sum() == 21
assert np.isclose(df.x.var(), np.var(np.arange(1., 7.), ddof=0))
assert np.isclose(df.x.std(), np.std(np.arange(1., 7.), ddof=0))
assert df.count() == 6
assert df.value.count() == 4
assert df.x.minmax().tolist() == [1., 6.]
assert df.group.nunique() == 2
correlation = df.correlation('x', 'y')
assert 0.9 < correlation <= 1
# Histogram-based approximation, not NumPy's exact linear-interpolation quantile:
quartiles = df.percentile_approx('x', percentage=[25, 50, 75], percentile_shape=4096)
assert np.all(np.diff(quartiles) >= 0)
assert 1 <= quartiles[0] <= quartiles[-1] <= 6
pending = [df.x.mean(delay=True), df.y.sum(delay=True)]
df.execute()
assert [p.get().item() for p in pending] == [3.5, 91.0]

std/var use the population denominator here (ddof=0), despite an imprecise "sample variance" docstring. For an unbiased sample variance, compute the valid count and multiply population variance by n/(n-1) only when n>1; this does not address weights, clustering or dependence. NaNs/nulls are skipped by many reductions; report the valid count for the exact expression and selection. Infinity is a separate case. unique() and high-cardinality groups allocate outputs proportional to cardinality. There is no .quantile() or .prod() expression method in this release.

Raw lines 74 to 102
103## Grouped and binned statistics
105```python
106summary = df.groupby('group', agg={
107 'rows': vaex.agg.count(),
108 'valid': vaex.agg.count('value'),
109 'sum_x': vaex.agg.sum('x'),
110 'mean_x': vaex.agg.mean('x'),
111 'range_x': vaex.agg.max('x') - vaex.agg.min('x'),
112}).sort('group')
113assert summary.rows.tolist() == [3, 3]
114assert summary.sum_x.tolist() == [9., 12.]
115assert summary.range_x.tolist() == [4., 4.]
116monthly = df.groupby(['year', 'month'], agg={'sum_x': vaex.agg.sum('x')})
117assert monthly.sum_x.sum() == 21
118counts = df.count(binby=['x', 'y'], limits=[[0., 7.], [0., 37.]], shape=(7, 4))
119means = df.mean('x', binby=['x', 'y'], limits=[[0., 7.], [0., 37.]], shape=(7, 4))
120assert counts.shape == (7, 4)
121assert counts.sum() == 6
122assert np.all(np.isnan(means[counts == 0]))
123```
125Keys in the aggregation dictionary name output columns; explicit aggregator objects
126bind each expression. Do not pass pandas-style `lambda group:` functions. Group
127order is not observation order. Sort a small summary by the real time coordinate,
128and represent missing time bins explicitly before drawing connecting lines.
129For grids, limits are intervals, not bin centers, and the last upper edge is excluded.
130Validate retained counts, including missing and out-of-range rows.

Grouped and binned statistics

summary = df.groupby('group', agg={
    'rows': vaex.agg.count(),
    'valid': vaex.agg.count('value'),
    'sum_x': vaex.agg.sum('x'),
    'mean_x': vaex.agg.mean('x'),
    'range_x': vaex.agg.max('x') - vaex.agg.min('x'),
}).sort('group')
assert summary.rows.tolist() == [3, 3]
assert summary.sum_x.tolist() == [9., 12.]
assert summary.range_x.tolist() == [4., 4.]
monthly = df.groupby(['year', 'month'], agg={'sum_x': vaex.agg.sum('x')})
assert monthly.sum_x.sum() == 21
counts = df.count(binby=['x', 'y'], limits=[[0., 7.], [0., 37.]], shape=(7, 4))
means = df.mean('x', binby=['x', 'y'], limits=[[0., 7.], [0., 37.]], shape=(7, 4))
assert counts.shape == (7, 4)
assert counts.sum() == 6
assert np.all(np.isnan(means[counts == 0]))

Keys in the aggregation dictionary name output columns; explicit aggregator objects bind each expression. Do not pass pandas-style lambda group: functions. Group order is not observation order. Sort a small summary by the real time coordinate, and represent missing time bins explicitly before drawing connecting lines. For grids, limits are intervals, not bin centers, and the last upper edge is excluded. Validate retained counts, including missing and out-of-range rows.

Raw lines 103 to 131
132## Joins, duplicates and filters
134```python
135lookup = vaex.from_arrays(group=np.array(['A', 'B']), offset=np.array([10., 20.]))
136assert lookup.group.nunique() == len(lookup)
137# extract fixes the row membership of the filtered view before joining.
138left = df[df.x >= 4].extract()
139joined = left.join(lookup, on='group', how='left')
140assert len(joined) == 3
141assert joined.offset.tolist() == [20., 10., 20.]
142inner = left.join(lookup[lookup.group == 'A'].extract(), on='group', how='inner')
143assert inner.x.tolist() == [5.]
144```
146Vaex `join` defaults to `how='left'`, supports left/right/inner, and uses one key
147expression per side (`on`, or `left_on`/`right_on`). Lists of key names are not a
148composite-key API. Use a documented upstream database/Arrow join for real composite
149keys, or a collision-free encoding with an explicit validation contract; concatenating
150strings with a separator can collide. Filter semantics are subtle: released docs warn
151that underlying rows may participate, so extract the intended populations first.
152Right-side duplicates can raise unless `allow_duplication=True`; enabling it can expand
153rows. Check row counts, unmatched/null keys, duplicates and scientific identity afterwards.
154Sorting and join indices consume RAM, even with memory-mapped source columns.

Joins, duplicates and filters

lookup = vaex.from_arrays(group=np.array(['A', 'B']), offset=np.array([10., 20.]))
assert lookup.group.nunique() == len(lookup)
# extract fixes the row membership of the filtered view before joining.
left = df[df.x >= 4].extract()
joined = left.join(lookup, on='group', how='left')
assert len(joined) == 3
assert joined.offset.tolist() == [20., 10., 20.]
inner = left.join(lookup[lookup.group == 'A'].extract(), on='group', how='inner')
assert inner.x.tolist() == [5.]

Vaex join defaults to how='left', supports left/right/inner, and uses one key expression per side (on, or left_on/right_on). Lists of key names are not a composite-key API. Use a documented upstream database/Arrow join for real composite keys, or a collision-free encoding with an explicit validation contract; concatenating strings with a separator can collide. Filter semantics are subtle: released docs warn that underlying rows may participate, so extract the intended populations first. Right-side duplicates can raise unless allow_duplication=True; enabling it can expand rows. Check row counts, unmatched/null keys, duplicates and scientific identity afterwards. Sorting and join indices consume RAM, even with memory-mapped source columns.

Raw lines 132 to 154