Coming from pandas¶
Daffodil keeps rows where pandas keeps columns. So building, selecting and changing rows is cheap, and math over a whole numeric column is better done in pandas or NumPy. See the Home page for when each fits, with measurements.
Some differences to know first:
daf[x]selects rows, wheredf[x]selects a column. A column isdaf[:, 'name'], or as a list,daf.col('name').- An integer in
[]is a position, as withiloc. Only the ends of a tuple range are keys. A key is a value of the keyfield column, which stays in the table, unlike the pandas index. - A selection of rows shares the rows with the original. Changing a cell in it changes the original. See Selecting and indexing.
- A cell can hold anything, and columns are not converted unless you ask. A missing value is
'', not NaN. - A table prints as Markdown.
The same task in each¶
Each line is the same task, not the same expression. The result type, and whether the table is changed, can differ. The notes say how.
| pandas | Daffodil | Notes |
|---|---|---|
pd.DataFrame(lod) |
Daf.from_lod(lod) |
|
pd.read_csv(path) |
Daf.from_csv(path) |
the cells are text until apply_dtypes() |
df.to_csv(path) |
daf.to_csv_file(path) |
|
df.to_dict('records') |
daf.to_lod() |
|
df.shape, len(df) |
daf.shape(), len(daf) |
|
df.empty |
not daf |
|
df.columns |
daf.columns() |
|
df.dtypes |
daf.dtypes |
describes, does not enforce |
df.astype(...) |
daf.apply_dtypes(dtypes=...) |
in place |
df.head(n), df.tail(n) |
daf[:n], daf[-n:] |
|
df.iloc[n] |
daf[n] |
a Daf. .to_dict() for a dict |
df.loc[k] |
daf.select_record(k) |
a dict. Needs a keyfield. daf[k] also works, but there an int k is a position |
df.set_index('id') |
daf.set_keyfield('id') |
the column stays in the table |
df['c'] |
daf[:, 'c'], or daf.col('c') for a list |
|
df[['a', 'b']] |
daf[:, ['a', 'b']] |
|
df[df.qty > 5] |
daf.select_where(lambda row: row['qty'] > 5) |
|
df[df.c.isin(vals)] |
daf.select_by_dict([{'c': v} for v in vals]) |
one pass, no list of bools |
df.drop(columns=[...]) |
daf.select_cols(exclude_cols=[...]) |
|
df[~df.id.isin(keys)] |
daf.select_krows(keys, inverse=True) |
|
df.loc[len(df)] = row, pd.concat |
daf.append(row) |
cheap |
pd.concat([df1, df2]) |
d1.append(d2) |
|
df.insert(i, 'c', vals) |
daf.insert_col('c', vals, icol=i) |
|
| (none) | daf.insert_irow(i, row) |
insert a row anywhere |
df['c'] = vals |
daf[:, 'c'] = vals |
|
df.rename(columns=...) |
daf.rename_cols({...}) |
|
df.sort_values('c') |
daf.copy().sort_by_colname('c') |
sort_by_colname() sorts the Daf itself. Copy first to keep the order |
df.T |
daf.transpose() |
a new Daf |
df.apply(f, axis=1) |
daf.apply(f) |
the row is a dict, and f returns one |
df.agg(...) |
daf.reduce(f) |
|
df.groupby('g') |
daf.groupby('g') |
a KeyedList of Dafs, used as a dict |
df.groupby('g')[cols].sum() |
daf.groupby_cols_reduce(['g'], Daf.sum_da, reduce_cols=cols) |
name the columns to add |
df.groupby([...])[cols].agg(f) |
daf.groupby_cols_reduce([...], f, reduce_cols=cols) |
|
df.value_counts('c') |
daf.valuecounts_for_colname('c') |
a dict |
df.merge(other, on='id') |
daf.join(other) |
on the keyfields, one to one. See Joins |
df.replace(old, new) |
daf.replace_in_columns(cols=None, find_values=[old], replacement=new) |
whole cells equal to old. find_replace() replaces a whole cell that matches a regex |
df.to_markdown() |
daf.to_md() |
|
df.to_numpy() |
daf.to_numpy() |
A short example¶
>>> from daffodil.daf import Daf
>>> daf = Daf.from_lod([{'id': 'a1', 'g': 'x', 'qty': 3}, {'id': 'b2', 'g': 'y', 'qty': 10},
... {'id': 'c3', 'g': 'x', 'qty': 7}], keyfield='id')
>>> daf[:2].col('id')
['a1', 'b2']
>>> daf.select_where(lambda row: row['qty'] > 5).col('id')
['b2', 'c3']
>>> daf.groupby_cols_reduce(['g'], Daf.sum_da, reduce_cols=['qty']).lol
[['x', 10], ['y', 10]]
>>> daf.sort_by_colname('qty', reverse=True).col('id')
['b2', 'c3', 'a1']