Flow Data
Inyo County, California
Abstract
This report documents water-year processing of LADWP monthly flow and production data, including data ingestion from ZRXP files, QA/QC checks, integration with historical totals, and preparation of OVGA-formatted deliverables.
Outputs include a refreshed totals-and-means table, OVGA upload files for flow and production, and QA/QC diagnostics for linked wells, wellfields, and stream gages. The processing steps below are organized to mirror the annual update workflow, from raw inputs to export-ready files.
Annual update instructions: See ANNUAL_UPDATE_GUIDE.md for comprehensive step-by-step instructions on updating this workflow for the next water year.
Data sources and preprocessing
WY2025 inputs are delivered as ZRXP ASCII files. The parser extracts the staid from the #TSPATH headers and reads monthly volume values. We then separate flow records (numeric staids) from production records (alpha staids), and join metadata needed for OVGA exports.
read data
file_path_flow <-here('data','hydro','2024-25 Water Year Transfer Packet for ICWD','VolumeMonthAF_2024-25.dat')
sum_stations_path <- here('data','hydro','2024-25 Water Year Transfer Packet for ICWD','2025 - Inyo County Summary Stations.xlsx')
sum_stations <- if (file.exists(sum_stations_path)) {
readxl::read_excel(sum_stations_path, sheet = "Data", skip = 2) %>%
rename(date = 1) %>%
mutate(date = as.Date(date))
} else {
tibble(date = as.Date(character()))
}
staidlist <- read_csv(here('data','hydro','staid_current.csv')) %>%
mutate(staid = as.character(staid))
ovga_points <- read_csv(here("data","hydro","Owens_Monitoring_Points.csv"))
production.meta <- read_csv(here('data','hydro','Production.csv')) %>%
select(mon_pt_name, method) %>% unique()
monthlies <- read_csv(here('data','hydro','totals_means_thru_sep_2024.csv'))
flow.meta <- read_csv(here('data','hydro','Flows.csv')) %>%
select(mon_pt_name, LADWP_ID) %>%
unique() %>%
mutate(staid = as.character(LADWP_ID)) %>%
select(-LADWP_ID)parse .dat ascii
parse_volume_dat <- function(file_path) {
dat_content <- readLines(file_path)
data_list <- list()
is_data_line <- function(line) {
grepl("\\d+ \\d+\\.\\d+", line)
}
staid <- NA_character_
for (line in dat_content) {
if (startsWith(line, "#TSPATH")) {
staid <- sub(".*/([^/]+)/VOLUME/.*", "\\1", line)
}
if (!startsWith(line, "#") && is_data_line(line) && !is.na(staid)) {
data_list <- c(data_list, list(data.frame(staid, dateread = line)))
}
}
dplyr::bind_rows(data_list)
}
ann.update <- parse_volume_dat(file_path_flow)last year raw flow
file_path_flow_prev <- here("data", "hydro", "2023-24 Water Year Transfer Packet for ICWD", "VolumeMonthAF_2023-24.dat")
ann.update_prev <- if (file.exists(file_path_flow_prev)) {
parse_volume_dat(file_path_flow_prev)
} else {
tibble(staid = character(), dateread = character())
}
ann.update_prev2 <- if (nrow(ann.update_prev) > 0) {
prev2 <- ann.update_prev %>% separate(dateread, c("date", "read"), sep = " ")
prev2$read <- as.numeric(prev2$read)
prev2
} else {
tibble(staid = character(), date = character(), read = numeric())
}Raw input coverage
The table below summarizes the raw input coverage for the current and prior water years. Use this to confirm record counts and date ranges before merging with historical totals.
raw input coverage
raw_flow_coverage <- tibble(
File = c(basename(file_path_flow), basename(file_path_flow_prev)),
Records = c(nrow(ann.update2), nrow(ann.update_prev2)),
Unique_staids = c(
dplyr::n_distinct(ann.update2$staid),
dplyr::n_distinct(ann.update_prev2$staid)
),
Date_start = c(
min(ymd_hms(ann.update2$date), na.rm = TRUE),
if (nrow(ann.update_prev2) > 0) min(ymd_hms(ann.update_prev2$date), na.rm = TRUE) else as.POSIXct(NA)
),
Date_end = c(
max(ymd_hms(ann.update2$date), na.rm = TRUE),
if (nrow(ann.update_prev2) > 0) max(ymd_hms(ann.update_prev2$date), na.rm = TRUE) else as.POSIXct(NA)
)
)
knitr::kable(raw_flow_coverage)| File | Records | Unique_staids | Date_start | Date_end |
|---|---|---|---|---|
| VolumeMonthAF_2024-25.dat | 8853 | 744 | 2024-10-01 | 2025-09-01 |
| VolumeMonthAF_2023-24.dat | 8796 | 744 | 2023-10-01 | 2024-09-01 |
date columns
ann.update2$datetime <- ymd_hms(ann.update2$date)
ann.update3<-ann.update2 %>%
mutate(year = year(datetime),
month = month(datetime),
month_abbr = month(datetime, label = TRUE, abbr = TRUE),
day = mday(datetime),
date.y.m.d = make_date(year, month,day))
ann.update4<-ann.update3 %>%
select(-date) %>%
rename(date = date.y.m.d) %>%
left_join(staidlist, by = 'staid') %>%
select(staid,description,read,date,year,month,month_abbr)
ann.update5 <- ann.update4 %>%
mutate(wy = case_when(month >= 10 ~ year + 1,
month <= 9 ~ year),
roy = case_when(month <= 3 ~ year - 1,
month >= 4 ~ year),
irrag = case_when(month %in% c(4,5,6,7,8,9) ~ year))summary stations
if (nrow(sum_stations) > 0) {
sstations <- sum_stations %>%
gather(staid,read,-date) %>%
left_join(staidlist, by = 'staid') %>%
select(-desc_id) %>%
mutate(date = as.Date(date),
datetime = as.Date(date),
month = month(datetime),
year = year(datetime),
month_abbr = month(datetime, label = TRUE, abbr = TRUE))
} else {
sstations <- ann.update4 %>% slice(0)
}Data integration and outputs
ICWD flow export
OVGA exports and QA/QC
OVGA flow export
Flow exports are derived from the monthly flow records after joining flow metadata and filtering to valid OVGA monitoring points. The output is formatted to the OVGA surface-water import template. The OVGA template reuses the “WellName” column for both wells and gages; in the flow export that column holds gage IDs (e.g. SW0373), not well names.
split flow vs production
normalize_flow_staid <- function(x) {
ifelse(str_detect(x, "^0"), str_replace(x, "^0+", ""), x)
}
flowdf <- tm %>%
filter(!str_detect(staid, "[[:alpha:]]")) %>%
mutate(staid = normalize_flow_staid(staid))
flow_ovga <- flowdf %>%
left_join(flow.meta, by = 'staid') %>%
filter(!is.na(mon_pt_name), mon_pt_name != "")
proddf <- tm %>% filter(str_detect(staid, "[[:alpha:]]"))ovga flow columns
sw.flow <- flow_ovga %>%
mutate(WellName = mon_pt_name,
FlowDate = date,
FlowRateAcFtPM = read,
FlowRateCFS = "",
FlowRateGPM = "") %>%
select(WellName,FlowDate,FlowRateAcFtPM, FlowRateCFS, FlowRateGPM)
sw.flow %>%
datatable(
options = list(pageLength = 10, lengthChange = TRUE, scrollX = TRUE),
class = "compact stripe"
)OVGA flow import metrics
flow_import_metrics <- tibble(
Metric = c(
"Flow records in upload",
"Unique monitoring points",
"Date range"
),
Value = c(
nrow(swflow2),
dplyr::n_distinct(swflow2$WellName),
paste0(min(swflow2$FlowDate, na.rm = TRUE), " to ", max(swflow2$FlowDate, na.rm = TRUE))
)
) %>% mutate(Value = format(Value, big.mark = ","))
knitr::kable(flow_import_metrics)| Metric | Value |
|---|---|
| Flow records in upload | 6611 |
| Unique monitoring points | 555 |
| Date range | 2024-10-01 to 2025-09-01 |
Flow export comparison (last year vs current)
This comparison highlights which flow gages were added or removed from the OVGA export between WY2024 and WY2025.
code
flow_export_last_path <- here("output", "ovga_swflow_import_wy24.csv")
flow_export_last <- if (file.exists(flow_export_last_path)) {
read_csv(flow_export_last_path, show_col_types = FALSE) %>% distinct(WellName)
} else {
tibble(WellName = character())
}
flow_export_current <- swflow2 %>% distinct(WellName)
flow_export_missing <- flow_export_last %>% anti_join(flow_export_current, by = "WellName")
flow_export_new <- flow_export_current %>% anti_join(flow_export_last, by = "WellName")
DT::datatable(
flow_export_missing,
options = list(pageLength = 10, lengthChange = TRUE),
caption = "Flow gages in WY2024 export but missing in WY2025 export"
)code
OVGA production export
Production exports are derived from monthly production records after joining production metadata and filtering to valid OVGA monitoring points. Missing method values are defaulted to “Unknown” to satisfy the OVGA template.
ovga production columns
prod_ovga <- proddf %>%
left_join(production.meta, by = c('staid' = 'mon_pt_name')) %>%
semi_join(ovga_points, by = c("staid" = "mon_pt_name"))
tm.well.flow <- prod_ovga %>%
mutate(WellName = staid,
StartDate = date,
EndDate = ceiling_date(date,'month'),
AgUsageAF = '',
MIUsageAF = read,
TotalUsageAF = read,
Method = if_else(is.na(method) | method == "", "Unknown", method),
Notes = '') %>%
select(WellName,StartDate,EndDate, AgUsageAF, MIUsageAF, TotalUsageAF, Method, Notes)code
find/replace duplicates
No duplicates found.
write ovga production
out_ovga <- here("output", "2025wy", "OVGA")
dir.create(out_ovga, recursive = TRUE, showWarnings = FALSE)
tm.well.flow %>% write_csv(file.path(out_ovga, paste0("ovga_production_import_wy", wy_range, ".csv")))
writexl::write_xlsx(tm.well.flow, file.path(out_ovga, paste0("ovga_production_import_wy", wy_range, ".xlsx")))OVGA production import metrics
production_import_metrics <- tibble(
Metric = c(
"Production records in upload",
"Unique production wells",
"Date range"
),
Value = c(
nrow(tm.well.flow),
dplyr::n_distinct(tm.well.flow$WellName),
paste0(min(tm.well.flow$StartDate, na.rm = TRUE), " to ", max(tm.well.flow$EndDate, na.rm = TRUE))
)
) %>% mutate(Value = format(Value, big.mark = ","))
knitr::kable(production_import_metrics)| Metric | Value |
|---|---|
| Production records in upload | 1956 |
| Unique production wells | 162 |
| Date range | 2024-10-01 to 2025-10-01 |
Production export comparison (last year vs current)
This comparison highlights which production wells were added or removed from the OVGA export between WY2024 and WY2025.
code
production_export_last_path <- here("output", "ovga_production_import_wy23-24.csv")
production_export_last <- if (file.exists(production_export_last_path)) {
read_csv(production_export_last_path, show_col_types = FALSE) %>% distinct(WellName)
} else {
tibble(WellName = character())
}
production_export_current <- tm.well.flow %>% distinct(WellName)
production_export_missing <- production_export_last %>% anti_join(production_export_current, by = "WellName")
production_export_new <- production_export_current %>% anti_join(production_export_last, by = "WellName")
DT::datatable(
production_export_missing,
options = list(pageLength = 10, lengthChange = TRUE),
caption = "Production wells in WY2024 export but missing in WY2025 export"
)code
Raw data exclusions (last year vs current)
These tables show staids present in the raw input data but excluded from the export, which helps isolate metadata gaps or OVGA list mismatches.
code
raw_flow_current <- ann.update2 %>%
filter(!str_detect(staid, "[[:alpha:]]")) %>%
mutate(staid = normalize_flow_staid(staid))
raw_flow_prev <- ann.update_prev2 %>%
filter(!str_detect(staid, "[[:alpha:]]")) %>%
mutate(staid = normalize_flow_staid(staid))
raw_prod_current <- ann.update2 %>%
filter(str_detect(staid, "[[:alpha:]]"))
raw_prod_prev <- ann.update_prev2 %>%
filter(str_detect(staid, "[[:alpha:]]"))
raw_flow_current_joined <- raw_flow_current %>%
left_join(flow.meta, by = "staid")
raw_flow_prev_joined <- raw_flow_prev %>%
left_join(flow.meta, by = "staid")
raw_flow_excluded_current <- raw_flow_current_joined %>%
filter(is.na(mon_pt_name) | !(mon_pt_name %in% flow_export_current$WellName)) %>%
distinct(staid, mon_pt_name) %>%
arrange(staid)
raw_flow_excluded_prev <- raw_flow_prev_joined %>%
filter(is.na(mon_pt_name) | !(mon_pt_name %in% flow_export_last$WellName)) %>%
distinct(staid, mon_pt_name) %>%
arrange(staid)
raw_prod_excluded_current <- raw_prod_current %>%
distinct(staid) %>%
filter(!staid %in% production_export_current$WellName) %>%
arrange(staid)
raw_prod_excluded_prev <- raw_prod_prev %>%
distinct(staid) %>%
filter(!staid %in% production_export_last$WellName) %>%
arrange(staid)
DT::datatable(
raw_flow_excluded_prev,
options = list(pageLength = 10, lengthChange = TRUE, scrollX = TRUE),
caption = "Flow staids in WY2024 raw data excluded from WY2024 export"
)code
code
code
Removed staids not on the basin list are excluded from OVGA exports and reflected in the raw‑data exclusion tables above.
OVGA flow and production exclusions (download)
flow/production staids excluded from OVGA exports
flow_exclusions <- flowdf %>%
left_join(flow.meta, by = "staid") %>%
filter(is.na(mon_pt_name) | mon_pt_name == "") %>%
group_by(staid) %>%
summarise(
records = n(),
first_date = min(date, na.rm = TRUE),
last_date = max(date, na.rm = TRUE),
mon_pt_name = dplyr::first(mon_pt_name),
.groups = "drop"
) %>%
left_join(staidlist, by = "staid") %>%
mutate(
dataset = "flow",
reason = "Missing flow metadata mapping (Flows.csv)"
) %>%
select(
dataset,
staid,
description,
records,
first_date,
last_date,
mon_pt_name,
reason
) %>%
arrange(staid)
prod_exclusions <- proddf %>%
anti_join(ovga_points, by = c("staid" = "mon_pt_name")) %>%
left_join(production.meta, by = c("staid" = "mon_pt_name")) %>%
group_by(staid) %>%
summarise(
records = n(),
first_date = min(date, na.rm = TRUE),
last_date = max(date, na.rm = TRUE),
method = dplyr::first(method),
.groups = "drop"
) %>%
left_join(staidlist, by = "staid") %>%
mutate(
dataset = "production",
reason = "Not in OVGA monitoring points list"
) %>%
select(
dataset,
staid,
description,
records,
first_date,
last_date,
method,
reason
) %>%
arrange(staid)
out_ovga <- here("output", "2025wy", "OVGA")
dir.create(out_ovga, recursive = TRUE, showWarnings = FALSE)
flow_exclusions %>%
write_csv(file.path(out_ovga, paste0("ovga_flow_exclusions_wy", wy_year, ".csv")))
writexl::write_xlsx(flow_exclusions, file.path(out_ovga, paste0("ovga_flow_exclusions_wy", wy_year, ".xlsx")))
prod_exclusions %>%
write_csv(file.path(out_ovga, paste0("ovga_production_exclusions_wy", wy_year, ".csv")))
writexl::write_xlsx(prod_exclusions, file.path(out_ovga, paste0("ovga_production_exclusions_wy", wy_year, ".xlsx")))
dtw_exclusions_path <- file.path(out_ovga, "ovga_dtw_exclusions_wy2025.csv")
if (file.exists(dtw_exclusions_path)) {
dtw_exclusions <- read_csv(dtw_exclusions_path, show_col_types = FALSE)
ovga_exclusions <- bind_rows(dtw_exclusions, flow_exclusions, prod_exclusions)
ovga_exclusions %>%
write_csv(file.path(out_ovga, paste0("ovga_exclusions_wy", wy_year, ".csv")))
writexl::write_xlsx(ovga_exclusions, file.path(out_ovga, paste0("ovga_exclusions_wy", wy_year, ".xlsx")))
}
DT::datatable(
flow_exclusions,
options = list(pageLength = 10, lengthChange = TRUE, scrollX = TRUE),
class = "compact stripe",
caption = "Flow staids excluded from OVGA export (WY2025)"
)flow/production staids excluded from OVGA exports
Results: QA/QC and plots
This section provides QA/QC plots and summaries for flow, production, and linked well pumping. It includes time-series plots, seasonal totals, and percentile comparisons to support review before export.
Totals and Means - summary stations requested
Summary-station series (OVR, OVPW, etc.) are populated from the summary stations file (2025 - Inyo County Summary Stations.xlsx). These plots now include data through WY2025.
OVR Owens Valley Runoff
OVPW Owens Valley Pumped Water
FTC L.A.A. Total Flow to the City
LOLU Lower Owens River Project – Lakes and Ponds Use
LOOU Lower Owens River Project – Operations
LORPDU Lower Owens River – Delta Uses
LORPRU Lower Owens River – River Uses
LORPTU Lower Owens River Project Total Uses
LOWU Lower Owens River Project – Waterfowl Uses
MBR Mono Basin Runoff
MTWP Mono Tunnel at West Portal
OLTU Owens Lake Total Uses
OVFG Owens Valley Flowing Groundwater
OVGR Owens Valley Groundwater Recharge
OVIR Owens Valley Irrigation
SHTO South Haiwee Total Outflow (1+2+3+BP)
Stream flow
Top 10 stream gages by WY2025 volume (water year Oct-Sep)
code
flow_wy <- flowdf %>%
mutate(wy = if_else(month >= 10, year + 1, year))
top_streams <- flow_wy %>%
filter(wy == wy_year) %>%
group_by(staid) %>%
summarise(total_af = sum(read, na.rm = TRUE), .groups = "drop") %>%
arrange(desc(total_af)) %>%
slice_head(n = 10) %>%
left_join(flow.meta, by = "staid")
top_streams <- top_streams %>%
mutate(display_name = if_else(is.na(mon_pt_name) | mon_pt_name == "", staid, mon_pt_name))
plots <- purrr::map2(top_streams$staid, top_streams$display_name, ~{
plot_title <- paste0(.y, " (", .x, ")")
htmltools::tagList(
htmltools::tags$h4(plot_title),
monthly_flow_syncplot_staid(staid_p = .x, data = totals_means, group_name = "a", title = plot_title)
)
})
htmltools::tagList(plots)SW0050 (50)
SW0077 (77)
SW2066 (2066)
SW0049 (49)
413 (413)
SW3289 (3289)
SW0118 (118)
SW0116 (116)
SW3324 (3324)
SW0088 (88)
Pumping
Runoff year (Apr-Mar) pumping totals are reported below. Water year (Oct-Sep) is used for the flow series above. RY2025 totals are partial because Oct–Mar 2025–26 data have not yet been delivered.
Production well locations and WY2025 pumping
production well map by annual pumping
# Calculate WY2025 total pumping by well
wy2025_pumping <- proddf %>%
filter(year == 2024 & month >= 10 | year == 2025 & month <= 9) %>%
mutate(read = if_else(read == -777, 0, read)) %>%
group_by(staid) %>%
summarise(
total_pumping_af = sum(read, na.rm = TRUE),
n_months = n(),
.groups = "drop"
) %>%
filter(total_pumping_af > 0)
# Join with monitoring points for locations
pumping_map_data <- wy2025_pumping %>%
left_join(ovga_points, by = c("staid" = "mon_pt_name")) %>%
filter(!is.na(mon_pt_lat), !is.na(mon_pt_long))
# Create leaflet map with circle markers sized by pumping volume
if (nrow(pumping_map_data) > 0) {
# Scale radius: sqrt transformation for better visual distribution
max_pumping <- max(pumping_map_data$total_pumping_af, na.rm = TRUE)
pumping_map_data <- pumping_map_data %>%
mutate(
radius = sqrt(total_pumping_af / max_pumping) * 20 + 5,
popup_text = paste0(
"<b>", staid, "</b><br>",
"WY2025 Pumping: ", format(round(total_pumping_af, 0), big.mark = ","), " AF<br>",
"Months reported: ", n_months
)
)
leaflet(pumping_map_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
lng = ~mon_pt_long,
lat = ~mon_pt_lat,
radius = ~radius,
color = "#2E86AB",
fillColor = "#2E86AB",
fillOpacity = 0.6,
stroke = TRUE,
weight = 1,
popup = ~popup_text,
label = ~staid
) %>%
addLegend(
position = "bottomright",
colors = c("#2E86AB", "#2E86AB", "#2E86AB"),
labels = c(
paste0("Small (<", format(round(max_pumping * 0.1, 0), big.mark = ","), " AF)"),
paste0("Medium (~", format(round(max_pumping * 0.5, 0), big.mark = ","), " AF)"),
paste0("Large (>", format(round(max_pumping * 0.9, 0), big.mark = ","), " AF)")
),
title = "WY2025 Total Pumping",
opacity = 0.8
)
} else {
cat("No production wells with location data available for mapping.")
}Annual pumping by wellfield
code
tozoo <- function(x) zoo(x$total_pumping, x$roy2)
dyMultiColumn <- function(dygraph) {
dyPlotter(dygraph = dygraph,
name = "MultiColumn",
path = system.file("plotters/multicolumn.js",
package = "dygraphs"))
}
plot_wellfield_total <- function(data, field_sites, linked_wells_sites, title, value_range = NULL) {
linked_wells <- linked_wells_sites %>%
filter(Site %in% field_sites) %>%
pull(Linked_Well) %>%
unique()
if (length(linked_wells) == 0) {
return(NULL)
}
df <- data %>%
filter(staid %in% linked_wells) %>%
mutate(read = if_else(read == -777, 0, read)) %>%
group_by(roy, staid) %>%
summarise(total_pumping = sum(read, na.rm = TRUE), .groups = "drop") %>%
mutate(roy2 = make_date(roy, 4, 1))
df <- pad_series_bounds(df, "roy2", "staid", months_start = 6, months_end = 18)
year_totals <- df %>%
group_by(roy2) %>%
summarise(total_pumping = sum(total_pumping, na.rm = TRUE), .groups = "drop")
y_max <- max(year_totals$total_pumping, na.rm = TRUE)
plot_data <- do.call(merge, lapply(split(df, df$staid), tozoo))
plot <- plot_data %>%
dygraph(main = title, group = "a") %>%
dyStackedBarGroup(linked_wells) %>%
dyBarChart() %>%
dyAxis("y", label = "Total Annual Pumping (AF)", axisLabelWidth = 70)
if (is.finite(y_max)) {
plot <- plot %>% dyAxis("y", valueRange = c(0, y_max * 1.05))
}
if (!is.null(value_range)) {
plot <- plot %>% dyAxis("y", valueRange = value_range)
}
plot
}
compute_linked_seasonal <- function(data, linked_wells_sites, group_map, group_col, target_year = 2025) {
base <- data %>%
filter(staid %in% linked_wells_sites$Linked_Well) %>%
mutate(
read = if_else(read < 0, 0, read),
date = as.Date(date),
year = year(date),
month = month(date),
wy = if_else(month >= 10, year + 1, year),
runoff_year = if_else(month >= 4, year, year - 1),
season = case_when(
month %in% 4:9 ~ "Apr-Sep",
month %in% c(10, 11, 12, 1, 2, 3) ~ "Oct-Mar",
TRUE ~ NA_character_
)
) %>%
left_join(group_map, by = c("staid" = "Linked_Well")) %>%
filter(!is.na(.data[[group_col]]))
seasonal_wy <- base %>%
group_by(.data[[group_col]], wy, season) %>%
summarise(total_pumping = sum(read, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(names_from = season, values_from = total_pumping) %>%
rename(Group = !!sym(group_col), WY = wy) %>%
arrange(Group, WY)
runoff_ry <- base %>%
group_by(.data[[group_col]], runoff_year) %>%
summarise(`Apr-Mar` = sum(read, na.rm = TRUE), .groups = "drop") %>%
rename(Group = !!sym(group_col), RY = runoff_year) %>%
arrange(Group, RY)
percentiles_wy <- seasonal_wy %>%
group_by(Group) %>%
mutate(
`Apr-Sep pct` = if_else(
is.na(`Apr-Sep`),
NA_real_,
round(dplyr::percent_rank(`Apr-Sep`) * 100, 1)
),
`Oct-Mar pct` = if_else(
is.na(`Oct-Mar`),
NA_real_,
round(dplyr::percent_rank(`Oct-Mar`) * 100, 1)
)
) %>%
ungroup() %>%
filter(WY == target_year) %>%
select(Group, WY, `Apr-Sep`, `Apr-Sep pct`, `Oct-Mar`, `Oct-Mar pct`)
percentiles_ry <- runoff_ry %>%
group_by(Group) %>%
mutate(
`Apr-Mar pct` = if_else(
is.na(`Apr-Mar`),
NA_real_,
round(dplyr::percent_rank(`Apr-Mar`) * 100, 1)
)
) %>%
ungroup() %>%
filter(RY == target_year) %>%
select(Group, RY, `Apr-Mar`, `Apr-Mar pct`)
list(
seasonal_wy = seasonal_wy,
runoff_ry = runoff_ry,
percentiles_wy = percentiles_wy,
percentiles_ry = percentiles_ry
)
}Linked Wells QA/QC
Linked-well pumping plots from the on/off management program support visual QA/QC of annual totals.
code
tm2 <- totals_means
linked_wells_sites <- data.frame(
Site = rep(
c(
"BP1", "BP2", "BP3", "BP4",
"LW1", "LW2", "LW3",
"TA3", "TA4", "TA5", "TA6",
"TS1", "TS2", "TS3", "TS4",
"IO1", "IO2",
"SS1", "SS2", "SS3", "SS4",
"BG2"
),
times = c(4, 4, 4, 1, 4, 4, 5, 4, 2, 1, 2, 1, 1, 3, 2, 3, 1, 3, 3, 2, 2, 4)
),
Linked_Well = c(
"W210", "W378", "W379", "W389", # BP1
"W220", "W229", "W374", "W375", # BP2
"W222", "W223", "W231", "W232", # BP3
"W331", # BP4
"W247", "W248", "W249", "W398", # LW1
"W236", "W239", "W243", "W244", # LW2
"W240", "W241", "W399", "W376", "W377", # LW3
"W106", "W110", "W111", "W114", # TA3
"W342", "W347", # TA4
"W349", # TA5
"W109", "W370", # TA6
"W159", # TS1
"W155", # TS2
"W103", "W104", "W382", # TS3
"W380", "W381", # TS4
"W061", "W391", "W400", # IO1
"W063", # IO2
"W069", "W392", "W393", # SS1
"W074", "W394", "W395", # SS2
"W092", "W396", # SS3
"W075", "W345", # SS4
"W076", "W403", "W343", "W348" # BG2
)
)
wellfield_sites <- tibble(
Wellfield = c(
"Laws",
"Big Pine",
"Taboose/Aberdeen",
"Thibaut/Sawmill",
"Independence/Oak",
"Symmes/Shepherd",
"Bairs/Georges"
),
Site = I(list(
c("LW1", "LW2", "LW3"),
c("BP1", "BP2", "BP3", "BP4"),
c("TA3", "TA4", "TA5", "TA6"),
c("TS1", "TS2", "TS3", "TS4"),
c("IO1", "IO2"),
c("SS1", "SS2", "SS3", "SS4"),
c("BG2")
))
) %>%
unnest(Site)
site_map <- linked_wells_sites %>% select(Linked_Well, Site)
wellfield_map <- linked_wells_sites %>%
left_join(wellfield_sites, by = "Site") %>%
select(Linked_Well, Wellfield)
seasonal_by_site <- compute_linked_seasonal(tm2, linked_wells_sites, site_map, "Site", target_year = wy_year)
seasonal_by_wellfield <- compute_linked_seasonal(tm2, linked_wells_sites, wellfield_map, "Wellfield", target_year = wy_year)Seasonal totals use water year (WY, Oct–Sep end year) for Apr–Sep and Oct–Mar. Runoff totals use runoff year (RY, Apr–Mar start year). Example: RY2025 = Apr 2025–Mar 2026.
Seasonal pumping totals by site (water year Oct-Sep)
Runoff year totals by site (Apr-Mar)
WY2025 percentiles by site (water year Oct-Sep)
Percentiles are calculated within each site using the historical distribution of Apr–Sep totals (and separately Oct–Mar totals). WY2025 percentiles therefore rank the 2025 six‑month total against all prior years for that site.
RY2025 percentiles by site (runoff year Apr-Mar)
Seasonal pumping totals by wellfield (water year Oct-Sep)
Runoff year totals by wellfield (Apr-Mar)
WY2025 percentiles by wellfield (water year Oct-Sep)
Percentiles are calculated within each wellfield using the historical distribution of Apr–Sep totals (and separately Oct–Mar totals). WY2025 percentiles therefore rank the 2025 six‑month total against all prior years for that wellfield.
RY2025 percentiles by wellfield (runoff year Apr-Mar)
Laws Monitoring Site #1 (LW1)
Laws Monitoring Site #2 (LW2)
Laws Monitoring Site #3 (LW3)
Laws Wellfield Total Pumping
Big Pine Monitoring Site #1 (BP1)
Big Pine Monitoring Site #2 (BP2)
Big Pine Monitoring Site #3 (BP3)
Big Pine Monitoring Site #4 (BP4)
Exempt Wells - Fish Hatchery and 218, 219
code
linked_wells <- c("W218","W219","W330","W332")
DF <- tm2 %>%
filter(staid %in% linked_wells) %>%
mutate(read = if_else(read == -777, 0, read)) %>%
group_by(roy, staid) %>%
summarise(total_pumping = sum(read, na.rm = TRUE), .groups = "drop") %>%
mutate(roy2 = make_date(roy, 4, 1))
DF <- pad_series_bounds(DF, "roy2", "staid", months_start = 6, months_end = 18)
Data <- do.call(merge, lapply(split(DF, DF$staid), tozoo))
year_totals_exempt <- DF %>%
group_by(roy2) %>%
summarise(total_pumping = sum(total_pumping, na.rm = TRUE), .groups = "drop")
y_max_exempt <- max(year_totals_exempt$total_pumping, na.rm = TRUE)
plot_exempt <- Data %>% dygraph(group = "a") %>%
dyStackedBarGroup(c("W218","W219","W330","W332")) %>%
dyBarChart() %>%
dyAxis("y", label = "Total Annual Pumping (AF)", axisLabelWidth = 70)
if (is.finite(y_max_exempt)) {
plot_exempt <- plot_exempt %>% dyAxis("y", valueRange = c(0, y_max_exempt * 1.05))
}
plot_exemptBig Pine Pumping Totals
code
linked_wells <- c("W210","W378","W379","W389","W220","W229","W374","W375","W222","W223","W231","W232","W331","W218","W219","W330","W332")
DF <- tm2 %>%
filter(staid %in% linked_wells) %>%
mutate(read = if_else(read == -777, 0, read)) %>%
group_by(roy, staid) %>%
summarise(total_pumping = sum(read, na.rm = TRUE), .groups = "drop") %>%
mutate(roy2 = make_date(roy, 4, 1))
DF <- pad_series_bounds(DF, "roy2", "staid", months_start = 6, months_end = 18)
Data <- do.call(merge, lapply(split(DF, DF$staid), tozoo))
year_totals_bp <- DF %>%
group_by(roy2) %>%
summarise(total_pumping = sum(total_pumping, na.rm = TRUE), .groups = "drop")
y_max_bp <- max(year_totals_bp$total_pumping, na.rm = TRUE)
plot_bp <- Data %>% dygraph(group = "a") %>%
dyStackedBarGroup(c("W210","W378","W379","W389","W220","W229","W374","W375","W222","W223","W231","W232","W331","W218","W219","W330","W332")) %>%
dyBarChart() %>%
dyAxis("y", label = "Total Annual Pumping (AF)", axisLabelWidth = 70)
if (is.finite(y_max_bp)) {
plot_bp <- plot_bp %>% dyAxis("y", valueRange = c(0, y_max_bp * 1.05))
}
plot_bpBig Pine Wellfield Pumping
Taboose/Aberdeen Monitoring Site #3 (TA3)
Taboose/Aberdeen Monitoring Site #4 (TA4)
Taboose/Aberdeen Monitoring Site #5 (TA5)
Taboose/Aberdeen Monitoring Site #6 (TA6)
Taboose/Aberdeen Wellfield Total Pumping
Thibaut/Sawmill Monitoring Site #1 (TS1)
Thibaut/Sawmill Monitoring Site #2 (TS2)
Thibaut/Sawmill Monitoring Site #3 (TS3)
Thibaut/Sawmill Monitoring Site #4 (TS4)
Thibaut/Sawmill Wellfield Total Pumping
Independence/Oak Monitoring Site #1 (IO1)
Independence/Oak Monitoring Site #2 (IO2)
Independence/Oak Wellfield Total Pumping
Symmes/Shepherd Monitoring Site #1 (SS1)
Symmes/Shepherd Monitoring Site #2 (SS2)
Symmes/Shepherd Monitoring Site #3 (SS3)
Symmes/Shepherd Monitoring Site #4 (SS4)
Symmes/Shepherd Wellfield Total Pumping
Bairs/Georges Monitoring Site #2 (BG2)
Bairs/Georges Wellfield Total Pumping
Citation
@report{county_water_department2026,
author = {County Water Department, Inyo},
publisher = {Inyo County Water Department},
title = {Flow {Data}},
date = {2026-01-31},
url = {https://inyo-gov.github.io/hydro-data/},
langid = {en}
}