The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Time Zones, Missing Hours and Daily Data

library(rmet)
x <- rmet_example

Hourly INMET data look simple, but three conventions decide whether daily values computed from them are right: how the hours are stamped, which day an hour belongs to, and what to do with missing hours. This vignette explains each one and how inmet_daily() handles it.

1. Timestamps are UTC and mark the end of the hour

The historical files record time in UTC. inmet_read() keeps it that way by default, so datetime is unambiguous for every station in the country:

x$datetime[1:3]
#> [1] "2023-01-01 00:00:00 UTC" "2023-01-01 01:00:00 UTC"
#> [3] "2023-01-01 02:00:00 UTC"
attr(x$datetime, "tzone")
#> [1] "UTC"

Many columns describe the hour that ends at the timestamp: precipitation is the total of the previous hour, and temp_max_prev_c and temp_min_prev_c are the highest and lowest air temperature during that hour. The record stamped 03:00 UTC therefore covers 02:00 to 03:00 UTC, which is 23:00 to midnight in Porto Alegre.

Displaying the same instants in local time only changes how they print:

local <- x$datetime[1:3]
attr(local, "tzone") <- "America/Sao_Paulo"
local
#> [1] "2022-12-31 21:00:00 -03" "2022-12-31 22:00:00 -03"
#> [3] "2022-12-31 23:00:00 -03"

2. Which day does an hour belong to?

A daily maximum in Brazil should be the maximum of a local day. Grouping by as.Date(datetime) uses UTC days instead, which in Porto Alegre run from 21:00 to 21:00 local time (22:00 to 22:00 during daylight saving time, which Brazil observed until 2019). An evening shower or a hot late afternoon can then land on the wrong day.

inmet_daily() assigns each record to the local day containing the middle of its hour (datetime minus 30 minutes). The two choices give different days:

utc_day <- as.Date(x$datetime)
local_day <- as.Date(x$datetime - 1800, tz = "America/Sao_Paulo")
mean(utc_day != local_day)
#> [1] 0.1624149

One hour in six (the records stamped 00:00 to 03:00 UTC) is placed on a different day, and the differences matter for daily totals and extremes:

wrong <- tapply(x$precip_mm, utc_day, sum)
right <- inmet_daily(x, min_hours = 1)
right <- setNames(right$precip_mm, right$date)
common <- intersect(names(wrong), names(right))
summary(abs(wrong[common] - right[common]))
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>   0.000   0.000   0.000   1.315   0.200  33.200

Brazil spans four time zones, and some states observed daylight saving time in some years and others not. By default (tz = "auto"), inmet_daily() uses each station’s own zone, derived from its state and longitude by inmet_timezone(), and reports it in the time_zone column:

inmet_timezone(c("RS", "MT", "AC", "AM"), c(-51.2, -56.1, -67.8, -69.9))
#> [1] "America/Sao_Paulo"  "America/Cuiaba"     "America/Rio_Branco"
#> [4] "America/Eirunepe"

Pass a fixed zone, such as tz = "America/Sao_Paulo", to put all stations on the same clock, or tz = "UTC" for UTC days.

3. Missing hours

INMET files contain rows for every hour of the year, including hours when a station was not reporting. By default inmet_read() drops rows in which every measurement is missing (drop_empty = FALSE keeps them). Individual variables can still be missing in the rows that remain.

A daily total over a day with half its hours missing is not a daily total. inmet_daily() therefore computes a variable only when it has at least min_hours valid hourly values that day (20 by default), and reports the number of hourly records in n_hours:

daily <- inmet_daily(x)
table(cut(daily$n_hours, c(0, 12, 19, 23, 25), right = TRUE))
#> 
#>  (0,12] (12,19] (19,23] (23,25] 
#>       2       2      46     316
daily[daily$n_hours < 20, c("date", "n_hours", "temp_max_c", "precip_mm")]
#>           date n_hours temp_max_c precip_mm
#> 1   2022-12-31       4         NA        NA
#> 124 2023-05-03      17         NA        NA
#> 148 2023-05-27       1         NA        NA
#> 149 2023-05-28      13         NA        NA

To see completeness before computing anything, inmet_completeness() gives the percentage of valid hours per station and month (or year):

head(inmet_completeness(x, variables = c("temp_dry_c", "precip_mm", "wind_speed_ms")))
#>   station_code  period hours_expected hours_present temp_dry_c precip_mm
#> 1         A801 2023-01            744           734       98.7      98.7
#> 2         A801 2023-02            672           666       99.1      99.1
#> 3         A801 2023-03            744           734       98.7      98.7
#> 4         A801 2023-04            720           720      100.0     100.0
#> 5         A801 2023-05            744           702       94.4      94.4
#> 6         A801 2023-06            720           720      100.0     100.0
#>   wind_speed_ms
#> 1          98.5
#> 2          99.1
#> 3          98.7
#> 4         100.0
#> 5          93.7
#> 6          99.7

Choose min_hours to suit the variable and the analysis: precipitation totals are sensitive to every missing hour, while a daily maximum of temperature is usually reached in the afternoon. Radiation is an exception: it is missing at night at most stations, so radiation_mj_m2 sums whatever daytime values are present (see ?inmet_daily).

4. Which temperature columns?

The hourly files contain both the air temperature at the top of the hour (temp_dry_c) and its extremes during the previous hour (temp_max_prev_c, temp_min_prev_c). The daily maximum and minimum should come from the extremes; the instantaneous values miss the peaks between readings:

from_extremes <- inmet_daily(x)$temp_max_c
from_hourly <- inmet_daily(x[, setdiff(names(x), "temp_max_prev_c")])$temp_max_c
summary(from_extremes - from_hourly)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
#>  0.0000  0.3000  0.5000  0.5707  0.8000  2.3000       4

At Porto Alegre in 2023 the hourly readings underestimate the daily maximum by about half a degree on average, and by more than two degrees on some days.

inmet_daily() uses the extremes when they are present and falls back to temp_dry_c otherwise.

5. Quality control

INMET labels automatic-station observations as raw data. inmet_qc() flags the most common problems: values outside physical limits, internally inconsistent records (such as an hourly maximum below the minimum), isolated spikes, and sensors stuck on the same value. It adds a <variable>_flag column naming the failed checks, and inmet_daily() and inmet_completeness() then ignore the flagged values:

checked <- inmet_qc(x)
#> Flagged 0 value(s) in 0 variable(s). See attr(x, "qc_summary").

Station A801 passed every check in 2023. To see what a flag looks like, plant a sensor spike of 15 degC in one hour:

y <- x
y$temp_dry_c[1000] <- y$temp_dry_c[1000] + 15
flagged <- inmet_qc(y, quiet = TRUE)
flagged[998:1002, c("datetime", "temp_dry_c", "temp_max_prev_c", "temp_dry_c_flag")]
#>                 datetime temp_dry_c temp_max_prev_c   temp_dry_c_flag
#> 998  2023-02-12 02:00:00       24.2            24.6              <NA>
#> 999  2023-02-12 03:00:00       23.6            24.3              <NA>
#> 1000 2023-02-12 04:00:00       38.4            23.8 consistency,spike
#> 1001 2023-02-12 05:00:00       22.7            23.5              <NA>
#> 1002 2023-02-12 06:00:00       21.9            22.6              <NA>

The reading is both a spike and inconsistent with the hour’s maximum. inmet_daily(flagged) leaves it out of the daily statistics, and attr(flagged, "qc_summary") counts the flags per variable and check. Across all 545 stations in the 2024 archive the same checks flag, for example, a stuck wind vane that reported 236 degrees for months and temperature readings that drop by 9 degC for one hour and return. Use action = "remove" to set flagged values to NA directly.

These checks catch gross errors only. Comparing with neighbouring stations (inmet_nearest()) remains the most effective way to find subtler problems.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.