Input parameters#
The reading functions share their arguments, and each one is described once,
here. read_data() and get_obs() take most of them;
get_recent_obs() and get_network_obs() take the ones their pages
list.
|
Read data from an instrument at a site. |
|
Get observations from a site. |
SID#
The site ID, such as "WBB" or "TRX01": a key of the
configuration. Case does not matter.
Sites and instruments lists them.
Instruments#
instruments (read_data()) is one instrument name, such as
"lgr_ugga", a list of them, or "all" (the default) for every instrument
at the site. The names are the keys under the site’s instruments in the
configuration; Sites and instruments lists them.
Pollutants#
pollutants (get_obs()) is one pollutant, such as "CO2", a list of
them, or "all" (the default) for every pollutant the site has measured.
Case does not matter: "nox", "NOx" and "NOX" all read NOx. A site’s
pollutants spell it 'NOX', and the columns NOx_ppb (see
Column names). Each pollutant brings only its own columns: "NO" gives
NO_ppb and not NO2_ppb or NOx_ppb, and "PM1" gives PM1_ugm3
and not PM10_ugm3.
Format#
format (get_obs()) is the shape of the table. "wide" (the
default) has a column for each concentration, such as CO2d_ppm_cal and
CH4d_ppm_cal. "long" has one row per measurement, with the column name
in pollutant and the measurement in value. Read data shows
both.
Research group#
group is the research group whose archive is read: "lin" or "horel"
(the keys of uataq.filesystem.groups), or a dictionary from instrument name
to group, to choose for each instrument.
With group=None (the default), each instrument picks its own: the lin group
when it operates the instrument, otherwise the group that does. When two groups’
archives cover different periods (the instrument’s group_dates in the
configuration), a range that crosses the boundary is
read from each group in turn, and the pieces are joined in time. TRX01’s ozone,
for example, comes from lin through 27 October 2017 and from horel after that.
Naming a group reads the whole range from that group.
Data level#
lvl (read_data()) is the data level: "raw", "qaqc",
"calibrated" or "final". With none (the default), read_data()
reads the highest level the instrument has. get_obs() takes no level: it
always reads final. What each level holds, and how to choose one, is on
Data levels and flags.
Time range#
time_range is the time to read, in UTC, like the times in the data. A range
includes its start and stops before its stop. To read in local time, see
A month in local time. It takes three forms.
None(the default)Everything the archive holds.
- A string
One period in ISO 8601, from its start to the start of the next one. It can name a year, a month, a day, an hour, a minute or a second, down to fractions of a second:
String
Range
"2020"the year 2020
"2020-01"January 2020
"2020-01-01"1 January 2020
"2020-01-01T12"12:00 to 13:00 on 1 January 2020
"2020-01-01T12:30"12:30 to 12:31
"2020-01-01T12:30:45.5"12:30:45.5 to 12:30:45.6 (one unit of the last digit given)
Dashes and colons may be left out (
"20200101"), and a trailingZor+00:00is accepted. Any other offset raises aValueError.- Two times
A list, tuple or slice of a start and a stop:
datetimeobjects, pandasTimestampobjects or strings as above. A stop given as a string is the end of the period it names, so["2020-01-01", "2020-01-31"]includes all of 31 January.Nonefor either end leaves that side open.
Time zones#
The output of the examples below is from runs on CHPC on 12 October 2026 (why it is recorded).
Times have no time zone attached. A string with an offset other than Z or
+00:00 is an error that says so:
>>> import uataq
>>> uataq.get_obs("WBB", "CO2", time_range="2025-06-01T00:00-06:00")
Traceback (most recent call last):
...
ValueError: Invalid time string '2025-06-01T00:00-06:00'. Expected ISO 8601 in UTC, e.g. '2024', '2024-01', '2024-01-15', '2024-01-15T12:30:45'.
A pandas Timestamp must not carry a time zone either. One that does gives
an error that does not say what to do:
>>> import pandas as pd
>>> start = pd.Timestamp("2025-06-01", tz="America/Denver")
>>> stop = pd.Timestamp("2025-07-01", tz="America/Denver")
>>> uataq.get_obs("WBB", "CO2", time_range=[start, stop])
Traceback (most recent call last):
...
TypeError: Cannot compare tz-naive and tz-aware timestamps
The same happens with tz="UTC". Convert the times to UTC and take the time
zone off:
>>> utc = [t.tz_convert("UTC").tz_localize(None) for t in (start, stop)]
>>> utc
[Timestamp('2025-06-01 06:00:00'), Timestamp('2025-07-01 06:00:00')]
>>> len(uataq.get_obs("WBB", "CO2", time_range=utc))
250685
That is June in Mountain time. A month in local time gets the same rows by converting a longer read and cutting it.
Where a range ends#
An end given as a Timestamp stops at that instant, so the same end given as
a string gives a longer range:
>>> a = uataq.get_obs("WBB", "CO2", time_range=["2025-06-02T06", "2025-06-03T06"])
>>> a.index[-1]
Timestamp('2025-06-03 06:59:53')
>>> b = uataq.get_obs(
... "WBB", "CO2", time_range=[pd.Timestamp("2025-06-02T06"), pd.Timestamp("2025-06-03T06")]
... )
>>> b.index[-1]
Timestamp('2025-06-03 05:59:58')
So the string range is an hour longer. When you want an end at an exact time,
give it as a Timestamp.
Number of processes#
num_processes is how many processes read an instrument’s files at once.
The default, 1, reads them one after another. A larger number uses at most one
process per file, and "max" uses every core this process may run on (in a
Slurm job, the cores you asked for). Keep it at 1 on a login node, which you
share with everyone logged in. A script that uses more than one process has its own
setup: see A year or more.
Recent#
recent (get_recent_obs()) is how far back from now to read: a
datetime.timedelta or text pandas reads as a time span, such as "3D" or
"12h". The default is 10 days.
File pattern#
file_pattern (read_data()) keeps only the files whose path contains
this text. It is a plain substring of the whole path, not a glob or a regular
expression, so "*gprmc*" matches nothing and the read finds no files. The
default, None, keeps every file.
Its main use is to pick the NMEA sentence type of the lin group’s raw GPS
files, which have a file for each (2024-06-01_gprmc.csv and
2024-06-01_gpgga.csv). The gprmc file has the date, status, speed and
course, and the gpgga file has the altitude, fix quality and number of
satellites. By default both are read and joined (the output is from a run on
CHPC on 30 October 2026):
>>> import uataq
>>> gps = uataq.read_data("TRX01", "gps", lvl="raw", time_range="2024-06-01")["gps"]
>>> gps.shape
(86400, 10)
>>> rmc = uataq.read_data(
... "TRX01", "gps", lvl="raw", time_range="2024-06-01", file_pattern="gprmc"
... )["gps"]
>>> rmc.shape
(86400, 9)
>>> sorted(set(gps.columns) - set(rmc.columns))
['Altitude_msl', 'Fix_Quality', 'N_Satellites']
Include GPS#
include_gps (a mobile site’s get_obs(), which
get_obs() passes it to) is True by default: each row gets the position
of the vehicle, and a row the GPS cannot place is left out (see
Months with no GPS fix). include_gps=False returns the measurements alone, as
a plain DataFrame, with every row. A fixed site has no GPS and ignores it.
For TRX01’s CO2 on 1 June 2024 (a run on CHPC on 30 October 2026):
>>> placed = uataq.get_obs("TRX01", "CO2", time_range="2024-06-01")
>>> alone = uataq.get_obs("TRX01", "CO2", time_range="2024-06-01", include_gps=False)
>>> type(placed).__name__, placed.shape
('GeoDataFrame', (8611, 12))
>>> type(alone).__name__, alone.shape
('DataFrame', (8622, 3))