🛫 Openair Preflight versió 1

Aquesta funció unifica la validació estructural i la correcció automàtica de dades per a la llibreria openair. Està dissenyada per evitar els errors més comuns de format que fan fallar els gràfics.

📦 La Funció Mestra

Copia i enganxa aquest codi a la teva sessió d'R:


# openair_preflight v2.1 (Edició Final Unificada)
openair_preflight <- function(
  df,
  key = c("date", "pollutant"),
  tz = "UTC",
  min_days = 21,
  autofix = TRUE,
  verbose = TRUE
) {
  # 0. Dependències internes
  if (!requireNamespace("dplyr", quietly = TRUE)) stop("Cal instal·lar 'dplyr'")
  if (!requireNamespace("lubridate", quietly = TRUE)) stop("Cal instal·lar 'lubridate'")
  
  library(dplyr)
  library(lubridate)

  report <- list()
  orig_rows <- nrow(df)

  if (verbose) message("--- Iniciant Preflight ---")

  # 1. Verificació de columnes obligatòries
  required <- c("date", "pollutant", "value")
  missing <- setdiff(required, names(df))
  if(length(missing) > 0) {
    stop("❌ Error Crític: Falten columnes: ", paste(missing, collapse = ", "))
  }

  # 2. Conversió de Tipus
  # Forcem el format POSIXct necessari per openair
  if(!inherits(df$date, "POSIXct")) {
    if(verbose) message("⚠️ 'date' no és POSIXct -> convertint...")
    df$date <- as.POSIXct(df$date, tz = tz)
  }
  
  # Forcem factor per a pollutants i numèric per a valors
  df$pollutant <- as.factor(df$pollutant)
  df$value     <- as.numeric(as.character(df$value))

  # 3. Gestió de NAs en camps clau
  na_count <- sum(is.na(df$date) | is.na(df$pollutant) | is.na(df$value))
  report$na_rows_removed <- na_count
  
  if(autofix && na_count > 0) {
    df <- df %>% filter(!is.na(date), !is.na(pollutant), !is.na(value))
    if(verbose) message("🔧 Eliminades ", na_count, " files amb dades incompletes.")
  }

  # 4. Ordre Cronològic
  if(is.unsorted(df$date)) {
    if(autofix) {
      df <- df[order(df$date), ]
      if(verbose) message("🔧 Dades ordenades cronològicament.")
    } else {
      warning("⚠️ L'ordre de les dates no és correcte. Això pot afectar els plots de sèries temporals.")
    }
  }

  # 5. Duplicats i Col·lapse (Lògica de mitjana)
  dup_check <- df %>% count(across(all_of(key))) %>% filter(n > 1)
  report$duplicate_groups <- nrow(dup_check)

  if(autofix && nrow(dup_check) > 0) {
    if(verbose) message("🔧 ", nrow(dup_check), " duplicats trobats -> calculant mitjana per clau...")
    df <- df %>%
      group_by(across(all_of(key))) %>%
      summarise(value = mean(value, na.rm = TRUE), .groups = "drop")
    
    # Netegem nivells de factors que hagin pogut quedar buits
    df$pollutant <- droplevels(df$pollutant)
  }

  # 6. Cobertura de dades
  coverage <- df %>%
    group_by(pollutant) %>%
    summarise(
      dies_actius = n_distinct(as.Date(date)),
      data_inici = min(date),
      data_fi = max(date),
      .groups = "drop"
    )
  
  report$coverage <- coverage
  
  insuficient <- coverage %>% filter(dies_actius < min_days)
  if(nrow(insuficient) > 0 && verbose) {
    message("⚠️ Alerta: Cobertura baixa (< ", min_days, " dies) a: ", 
            paste(insuficient$pollutant, collapse = ", "))
  }

  # 7. Resum de sortida
  if(verbose) {
    message("✅ Preflight finalitzat amb èxit.")
    message("   Files inicials: ", orig_rows)
    message("   Files finals:   ", nrow(df))
    message("   Pollutants:     ", paste(levels(df$pollutant), collapse = ", "))
  }

  return(list(data = df, report = report))
}

🛠️ Flux de Treball Recomanat

1. Càrrega: Importa el teu CSV o Excel a un dataframe anomenat df.
2. Preflight: Executa res <- openair_preflight(df).
3. Extracció: Utilitza res$data per als teus anàlisis.
4. Visualització: timeVariation(res$data, pollutant = "NO2").
Nota sobre duplicats: Si tens duplicats "reals" (per exemple, dues estacions diferents en el mateix dataframe), recorda incloure la columna de l'estació al paràmetre key:
openair_preflight(df, key = c("date", "pollutant", "site")).