When there is nothing to read#
Note
The output under each example on this page is from a run on CHPC on 15 October 2026, except under “Pollutant or site not found” and “A level asked of get_obs” (19 October 2026) and under “A group that does not log the instrument” (30 October 2026). The archive keeps growing, so what a read returns will have moved on by the time you read this (why the output is recorded).
The archive has gaps. Sites close, instruments are removed or break, and some months are missing. A read then raises an error or comes back empty. Each case below says what you see, what it means and what to do.
First, turn on the log. The package reports why it skipped something as a warning, and Python shows nothing from a library unless you ask:
import logging
logging.basicConfig(level=logging.WARNING)
The log lines are shown with each case below.
The examples start from these lines:
>>> import uataq
>>> from uataq import Network
>>> wbb = uataq.get_site("WBB")
No data found#
>>> wbb.get_obs("CO2", time_range="2021-08")
WARNING:uataq.sites:Error reading lgr_ugga@WBB data from lin groupspace: No files found within the specified time range (files run from 2014-02 to 2026-10, with gaps).
Traceback (most recent call last):
...
uataq.errors.ReaderError: No final data found for CO2 at WBB: lgr_ugga in lin groupspace: No files found within the specified time range (files run from 2014-02 to 2026-10, with gaps).
Nothing in the archive holds data for that instrument, in that range, at the
level that was read. get_obs and get_recent_obs read the final level.
Three cases that gave this:
A month with no final file (WBB, August 2021, above).
Dates after a site stopped sending data but before its instruments have a removal date in the configuration. Once they have one, these dates raise the error under Instrument inactive instead.
Dates in the future.
The message names what you asked for (CO2), the level that was read and the
site. Then it says, for each instrument that measures it (lgr_ugga
measures CO2 and CH4), what was found. lin is the research group whose
archive was searched (see Research group). The brackets say which files
exist: the first and last period they cover (a month at the final level), and
“with gaps” when a period in between has none, as August 2021 has none here.
When the message lists several instruments, name in lin groupspace means it
was looked for and not found, and name not read: ... means it was skipped,
with the reason.
A file can hold no rows, so the periods are not the days with data. To see those,
use Network.get_availability(), and add "qaqc" to lvls to see
whether the instrument measured at all.
Instrument inactive#
>>> wbb.get_obs("CO2", time_range="2010")
WARNING:uataq.sites:Not reading lgr_ugga@WBB: lgr_ugga@WBB is inactive in given time_range
Traceback (most recent call last):
...
uataq.errors.ReaderError: No final data found for CO2 at WBB: lgr_ugga not read: lgr_ugga@WBB is inactive in given time_range.
The instrument was not installed at the site in that range. Its window is its
active_range (see Which sites measure what; Sites and instruments
lists every instrument’s dates). A range that only touches the window, such
as one that ends the day the instrument was installed, counts as outside it. A
range that overlaps the window only in part is cut down to it, with no error.
Dates after a site was taken down give this error too: HDP after 7 February
2025, and SUG’s CO2 after 22 August 2025. That includes
get_recent_obs("HDP", pollutants="CO2"): nothing is recent at a site that
stopped.
Zero rows, no error#
>>> wbb_obs = wbb.get_obs("CO2", time_range="2024-03")
>>> print(len(wbb_obs))
206114
>>> print(len(wbb.get_obs("CO2", time_range="2024-03-04")))
<stdin>:1: EmptyResultWarning: No final data found for CO2 at WBB (2024-03-04 00:00:00 to 2024-03-05 00:00:00): the files for lgr_ugga exist but hold no data in that range.
0
The month has a file, but there are no rows on that day, at any level (raw
too): the instrument recorded nothing. The read did not fail, so you get an empty
table and a warning, uataq.errors.EmptyResultWarning. It is a Python warning,
so it shows without the log. get_recent_obs and get_network_obs warn the
same way when their table has no rows, and so does get_obs at a mobile site
whose rows found no GPS fix (see Months with no GPS fix). To silence it, for a loop
over days that may be empty:
import warnings
import uataq
warnings.filterwarnings("ignore", category=uataq.errors.EmptyResultWarning)
To see where the gaps in a month are, look at the time between rows:
>>> import pandas as pd
>>> gaps = wbb_obs.index.to_series().diff()
>>> gaps[gaps > pd.Timedelta("6h")]
Time_UTC
2024-03-05 19:29:38 2 days 06:00:42
2024-03-20 00:42:51 0 days 22:58:15
2024-03-25 18:02:30 2 days 14:47:56
Name: Time_UTC, dtype: timedelta64[us]
Each row is the time the data resumed and how long the gap before it was.
One of several pollutants missing#
>>> both = wbb.get_obs(["CO2", "O3"], time_range="2021-08")
WARNING:uataq.sites:Error reading lgr_ugga@WBB data from lin groupspace: No files found within the specified time range (files run from 2014-02 to 2026-10, with gaps).
<stdin>:1: DroppedPollutantWarning: No final data found for CO2 at WBB (2021-08-01 00:00:00 to 2021-09-01 00:00:00): lgr_ugga gave none, so the table leaves out CO2.
>>> both.columns.tolist()
['O3_ppb', 'instrument']
When you ask for several pollutants, the ones with data come back and the ones
without are left out. A warning, uataq.errors.DroppedPollutantWarning, names
each of them and the instruments that gave none. It is a Python warning, so it
shows without the log. To see why an instrument gave none, ask for that
pollutant alone: the error is the one under No data found. A read of several
raises that error only when none of them has data. To silence the warning:
import warnings
import uataq
warnings.filterwarnings("ignore", category=uataq.errors.DroppedPollutantWarning)
A pollutant is left out only when none of the instruments that measure it gave
data (in 2022, WBB’s NO2 comes from two). get_recent_obs warns the same way.
get_obs() with no pollutants named does not: “all” includes every pollutant
the site ever measured, and an instrument removed from the site is expected to
give none, so check the columns you got.
To go through many sites or months and skip the empty ones, catch the error:
>>> from uataq.errors import ReaderError
>>> for sid in ["WBB", "SUG", "HDP"]:
... try:
... obs = uataq.get_obs(sid, "CO2", time_range="2025-06")
... print(sid, len(obs), "rows")
... except ReaderError:
... print(sid, "has no CO2 data in June 2025")
...
WBB 250687 rows
WARNING:uataq.sites:Not reading licor_6262@SUG: licor_6262@SUG is inactive in given time_range
SUG 239760 rows
WARNING:uataq.sites:Not reading lgr_ugga@HDP: lgr_ugga@HDP is inactive in given time_range
HDP has no CO2 data in June 2025
The two WARNING lines are the log, which is on here. SUG’s licor_6262 was
replaced by the licor_7000, which gave the rows, so the first line is not a
problem.
Pollutant or site not found#
>>> uataq.get_obs("WBB", "NH3", time_range="2025-06")
Traceback (most recent call last):
...
ValueError: Invalid pollutant(s): NH3. WBB measures BC, CH4, CO, CO2, NO, NO2, NOx, O3, PM2.5.
>>> uataq.get_obs("CSP", "O3", time_range="2025-06")
Traceback (most recent call last):
...
ValueError: Not measured at CSP: O3. CSP measures CH4, CO2.
>>> uataq.get_obs("WBBB", "CO2", time_range="2025-06")
Traceback (most recent call last):
...
ValueError: Site 'WBBB' not found in the configuration file.
The first message is for a name that no uataq instrument measures: a
misspelling, or a molecule uataq does not read. The second is for a pollutant
that other instruments measure but this site does not. Both end with what the
site does measure, spelled as in the columns. You can also print
sorted(site.pollutants). For a site, check the ID against
uataq.laboratory.sites. The case does not matter for either.
read_data has a similar error when you name an instrument the site does not
have (InstrumentNotFoundError, which lists the site’s instruments).
A level asked of get_obs#
>>> uataq.get_obs("WBB", "CO2", lvl="qaqc", time_range="2025-06")
Traceback (most recent call last):
...
TypeError: get_obs() got an unexpected keyword argument 'lvl'. get_obs always reads the final level; to read another, call read_data(..., lvl='qaqc').
get_obs takes no level: it always reads final. level= raises the same
error. To read another level, call read_data, which gives a dictionary with
one table for each instrument:
uataq.read_data("WBB", "lgr_ugga", lvl="qaqc", time_range="2025-06").
The four levels says what each level is for.
A group that does not log the instrument#
>>> uataq.get_obs("WBB", "CO2", group="horel", time_range="2025-06")
WARNING:uataq.sites:Error reading lgr_ugga@WBB data from horel groupspace: horel group invalid for lgr_ugga@WBB
Traceback (most recent call last):
...
uataq.errors.ReaderError: No final data found for CO2 at WBB: lgr_ugga in horel groupspace: horel group invalid for lgr_ugga@WBB.
>>> uataq.get_obs("WBB", "CO2", group="hore1", time_range="2025-06")
Traceback (most recent call last):
...
ValueError: Invalid group: hore1
group (see Research group) is "lin" or "horel". The first error
is for a group that does not log that instrument: the lin group logs every
instrument at WBB, and the horel group none. The second is for a name that is
neither group. A site’s groups (sorted(wbb.groups) is ['lin'], and
the Groups column of Sites and instruments lists them) says
which archives log it. Leave group out and each instrument picks its own.
A level the instrument does not have#
>>> uataq.read_data("WBB", "teledyne_t400", lvl="calibrated", time_range="2025-06")
WARNING:uataq.sites:Error reading teledyne_t400@WBB data from lin groupspace: teledyne_t400@WBB has no calibrated level (it has raw, qaqc and final).
Traceback (most recent call last):
...
uataq.errors.ReaderError: No calibrated data found for ['teledyne_t400'] at WBB: teledyne_t400 in lin groupspace: teledyne_t400@WBB has no calibrated level (it has raw, qaqc and final).
Not every instrument has every level, and the message lists the ones this
instrument has, from least to most processed. Only the lin group’s CO2 and CH4
analyzers have a calibrated level, and not TRX01’s lgr_ugga_manual_cal
(see When _cal is empty), so every other instrument, and every instrument in the
horel archive, gives this error for it. Leave lvl out to read the highest
level the instrument has.
When read_data names several instruments, one without the level is logged
and left out, and the others are returned. The error is raised only when none
has it:
>>> data = uataq.read_data("WBB", ["lgr_ugga", "teledyne_t400"], lvl="calibrated", time_range="2025-06")
WARNING:uataq.sites:Error reading teledyne_t400@WBB data from lin groupspace: teledyne_t400@WBB has no calibrated level (it has raw, qaqc and final).
>>> list(data)
['lgr_ugga']
A level that is not one of raw, qaqc, calibrated or final raises
ValueError: Invalid data level and lists the four.
Network surprises#
>>> uataq.get_network_obs(["HDP"], "CO2", time_range="2025-06")
WARNING:uataq.sites:Not reading lgr_ugga@HDP: lgr_ugga@HDP is inactive in given time_range
WARNING:uataq.network:Error reading data from HDP: No data found for ['lgr_ugga'] at HDP: lgr_ugga not read: lgr_ugga@HDP is inactive in given time_range.
Traceback (most recent call last):
...
uataq.errors.ReaderError: No data found for CO2 in any site.
None of the sites had data in the range. The log says why for each site.
>>> Network(["WBB", "WBBB"], "CO2")
WARNING:uataq.network:Site 'WBBB' not found: Site 'WBBB' not found in the configuration file.
Network(sites=['WBB'], pollutant='CO2')
>>> Network(["CSP"], "O3")
Traceback (most recent call last):
...
ValueError: None of the sites given (CSP) measure O3. Sites that measure it: BUS01, BUS02, BUS03, BUS04, BUS05, BUS06, BUS07, BUS08, BUS09, BUS10, BUS11, BUS12, BUS13, BUS14, BUS15, TRX01, TRX02, TRX03, WBB.
A misspelled site, or a site that does not measure the pollutant, is dropped from
a Network without an error. The log warns about a misspelled site; a site
that does not measure the pollutant is dropped without a warning. Print the
Network, or read its sites attribute, to see the sites it kept; a loop
over net.sites reaches only those. If none is left you get the second error,
which names the sites you gave (only their number when there are more than
ten), then lists the sites in the configuration that measure the pollutant, or
says that none does. A site named twice, in any case ("WBB" and "wbb"), is
read once.
If the error is about time_range itself, see Time range.