Types and conversion¶
A cell can hold any Python object: a number, text, a list, a dict, a set, or another Daf. Nothing limits what a cell holds, and nothing is converted unless you ask.
dtypes¶
dtypes is a dict of column name to type. It says how to convert the columns when you ask,
as when reading a CSV file. It is not a schema that checks or forces the cells, and a Daf
built with dtypes keeps its cells as they are.
Three steps are separate:
| Step | What it does |
|---|---|
set dtypes, or call set_dtypes() |
declares the types. Nothing is converted |
apply_dtypes() |
tries to convert the cells |
check_schema() |
finds the cells that do not fit their dtypes, and raises |
Conversion is permissive. Text that is not a number stays text, with no error. So after
apply_dtypes(), an int column can still hold some text. Call check_schema() when you
need to know that every cell fits.
Converting text from a CSV file¶
A CSV file gives text. apply_dtypes() converts the columns in place, and returns the Daf:
>>> from daffodil.daf import Daf
>>> daf = Daf.from_csv_buff('id,qty,price,tags\na1,3,1.5,"[1, 2]"\nb2,,2.0,[]\n')
>>> daf.lol
[['a1', '3', '1.5', '[1, 2]'], ['b2', '', '2.0', '[]']]
>>> _ = daf.apply_dtypes(dtypes={'id': str, 'qty': int, 'price': float, 'tags': list})
>>> daf.lol
[['a1', 3, 1.5, [1, 2]], ['b2', '', 2.0, []]]
- An empty cell is NULL, the empty string, and stays NULL, except in a
boolcolumn. See Missing values below. - A list or dict column is read back from its text, which may be JSON or PYON.
- Text that does not make an
intor afloatis kept as it is. cols=converts only the named columns. Converting a column costs time, so convert only the columns you use.
>>> e = Daf.from_csv_buff('id,qty,note\na1,3,x\n')
>>> _ = e.apply_dtypes(dtypes={'id': str, 'qty': int, 'note': str}, cols=['qty'])
>>> e.lol
[['a1', 3, 'x']]
Load the file as text, then call apply_dtypes() for the columns you need. from_csv() and
from_csv_buff() still take dtypes=, and convert as they load, but that is deprecated.
bool columns¶
A bool column gets 0 or 1. The usual words for true and false are read in lower case,
capitalized or upper case. An empty cell becomes 0, unlike in an int or float column.
Other text stays as it is. Note that 'False' gives 0, where Python's bool('False') is True.
>>> flags = Daf(cols=['ok'], lol=[[''], ['0'], ['1'], ['False'], ['True'], ['no'], ['maybe']])
>>> _ = flags.apply_dtypes(dtypes={'ok': bool})
>>> flags.col('ok')
[0, 0, 1, 0, 1, 0, 'maybe']
The words for 0 are 0, false, no, n, f and off. The words for 1 are 1, true,
yes, y, t and on.
Missing values¶
A missing value is NULL, the empty string ''. It prints as nothing, so a table with missing
values stays easy to read.
Daffodil fills a missing cell with NULL, as when a dict given to append() leaves out a
column. But it does not turn other values into NULL. A None that you store
stays None, and from_pandas_df() keeps NaN as NaN. Neither is NULL, so is NULL is False
for both. Replace them yourself if you want NULL.
- The sum methods skip it.
- The value counts include it, as a count for
'', unless you passomit_nulls=True. to_pandas_df()keeps it as'', so a number column with NULL becomes an object column. Passdefault=Noneto get NaN in its place. That writes None into the Daf itself. See to_pandas_df() for a route that leaves the Daf alone.to_numpy()makes one array of one dtype, so a column with NULL becomes text.
>>> gaps = Daf(cols=['g', 'n'], lol=[['x', 1], ['', 2], ['x', '']])
>>> gaps.sum(['n'])
{'n': 3.0}
>>> gaps.valuecounts_for_colname('g'), gaps.valuecounts_for_colname('g', omit_nulls=True)
({'x': 2, '': 1}, {'x': 2})
Testing for NULL¶
Test a cell with is NULL. It is faster than == NULL, by about three times for a number
cell, since == must first try to compare the number with text. It works because Python
keeps one shared empty string.
>>> from daffodil.daf import NULL
>>> [val is NULL for val in gaps.col('n')]
[False, False, True]
- The import is not needed.
NULL = ''in your own module gives the same object. - Do not write
val is ''. Python warns aboutiswith a literal, so use the name. is NULLmisses an empty NumPy string, or an empty str subclass, written into a cell. Daffodil's own loaders and converters never make these.
Writing a CSV file¶
to_csv_file(path) writes a CSV file, and to_csv_buff() returns the text. A list, dict,
set or tuple in a cell is written as PYON, the Python form of the value, and is read back by
apply_dtypes():
>>> f = Daf(cols=['id', 'tags'], lol=[['a', [1, 2]], ['b', {'k': (1, 2)}]])
>>> print(f.to_csv_buff())
id,tags
a,"[1, 2]"
b,"{'k': (1, 2)}"
PYON is like JSON, but it can hold sets, tuples, and dicts with keys that are not text. See PYON.
To and from other forms¶
| To | From |
|---|---|
to_lod(): a list of dicts |
Daf.from_lod() |
to_dod(): a dict of dicts, by key |
Daf.from_dod() |
to_cols_dol(): a dict of column lists |
Daf.from_cols_dol() |
to_json() |
Daf.from_json() |
to_md(): a Markdown table |
Daf.from_md() |
to_pandas_df() |
Daf.from_pandas_df() |
to_numpy() |
Daf.from_numpy() |
to_csv_file(), to_csv_buff() |
Daf.from_csv(), Daf.from_csv_buff(), Daf.from_excel_buff(), Daf.from_ods() |