Full tutorial

Author

Joseph Gaudard

Importing the data

In this section we will use the data in the ex_data folder. More ressources on data import are available in this vignette on the fluxible website.

Importing log file

Importing the log and turning it into a dataframe with chamber ID (port nb), chamber closing time and re-opening time.

library(tidyverse)

# building col names
chamber_colnames <- c(
    "port", "valvestatus", "chamberstatus",
    "aux1", "aux2", "aux3", "aux4", "aux5",
    "temperaturev", "pressure"
    )
log_colnames <- c(
    "epochtime",
    rep(chamber_colnames, times = 12)
)

chamber_log_read <- list.files(
    "ex_log",
    full.names = TRUE
) |>
    map_dfr(
        read_log,
        col_names = log_colnames
    )
# read_log("ex_log/FRMonitor_0012.log", col_names = log_colnames)
# repeated colnames are normal
chamber_log_all <- chamber_log_read |>
    pivot_longer(!c(epochtime), names_to = c(".value", "variable"), names_sep = "_") |>
    filter(
        port %in% c(1:12) # we filter out all the rows with port -1
    ) |>
    arrange(epochtime) |> # just to be sure
    mutate( # without grouping
        chamber = case_when(
            chamberstatus %in% c(1:3) ~ "open",
            chamberstatus == 0 ~ "closed"
        ),
        change_id = consecutive_id(port, chamberstatus), #detecting if same port but new measurement
        datetime = as_datetime(epochtime) # we work in datetime
    ) |>
    filter(
        # chamberstatus %in% c(1:3) # very conservative, we can adjust the focus window later in flux_fitting
        chamberstatus == 1
        # valvestatus == 10
    ) |>
    mutate(
        measurement_id = consecutive_id(change_id) # just getting rid of the missing id after filter
        )
    
chamber_log <- chamber_log_all |>
    mutate(
        .by = c(measurement_id),
        closing = min(datetime) - 300, # can be recut in flux_fitting, but so we see better
        opening = max(datetime) + 300
    ) |>
    select(measurement_id, port, closing, opening) |>
    distinct()

# We make a separate df for temp and pressure so we keep the 4 seconds reads
chamber_temp_pres <- chamber_log_all |>
    mutate(
        air_temp = temperaturev * 15, # need to ask the exact conversion factor, for now this one makes sense
        pressure = pressure / 101.325 # need atm for fluxible
    ) |>
    select(datetime, air_temp, pressure)

# View(chamber_log_all)

Possible improvements

  • take into account the chamber status better and use it to by-pass the start and end cuts in flux_fitting. Opening and closing status would be “cut” (not used for fitting but visible on the plot), and fully open would be “keep”.
  • keep the other columns
  • Because of the inconsistant offset between the closing of the chamber and the start of the flux, it is difficult to make a proper record of start/end of fluxes.

Importing data file

data_read <- list.files(
    "ex_data",
    full.names = TRUE
) |>
    map_dfr(
        read_table
    )
# read_table("ex_data/JFAADS2294-20241211-193921-DataLog_User.dat")

data <- data_read |>
    mutate(
        f_datetime = as_datetime(paste(DATE, TIME))
    ) |>
    left_join(chamber_temp_pres, by = join_by(f_datetime == datetime)) |> # adding air temp and pressure here
    select(f_datetime, CO2_dry, air_temp, pressure) # we keep it simple for now and work only on CO2

# View(data)

Processing the data

library(fluxible)

conc <- flux_match(
    raw_conc = data,
    field_record = chamber_log,
    f_datetime = f_datetime,
    start_col = closing,
    end_col = opening,
    fixed_length = FALSE,
    time_diff = -15000 # 4h10
) |>
    drop_na(CO2_dry)
# View(conc)
conc_fit <- flux_fitting(
    conc_df = conc,
    f_conc = CO2_dry,
    fit_type = "linear",
    # fit_type = "exp_zhao18",
    start_cut = 300,
    end_cut = 300
)
#> Warning in flux_fitting(conc_df = conc, f_conc = CO2_dry, fit_type = "linear", : 
#>  fluxID 45 : slope was estimated on 216 points out of 857 seconds
#>  fluxID 53 : slope was estimated on 851 points out of 861 seconds
#>  fluxID 66 : slope was estimated on 852 points out of 853 seconds
#>  fluxID 69 : slope was estimated on 172 points out of 853 seconds
#>  fluxID 70 : slope was estimated on 849 points out of 856 seconds
#>  fluxID 71 : slope was estimated on 851 points out of 857 seconds
#>  fluxID 72 : slope was estimated on 845 points out of 853 seconds
#>  fluxID 87 : slope was estimated on 859 points out of 860 seconds
#>  fluxID 89 : slope was estimated on 848 points out of 849 seconds
# View(conc_fit)
conc_flags <- flux_quality(
    slopes_df = conc_fit,
    f_conc = CO2_dry,
    force_discard = 72 # obviously a complete mismatch
)
#> 
#>  Total number of measurements: 55
#> 
#>  ok   30      55 %
#>  discard      21      38 %
#>  zero     3   5 %
#>  force_discard    1   2 %
#>  start_error      0   0 %
#>  no_data      0   0 %
#>  force_ok     0   0 %
#>  force_zero   0   0 %
#>  force_lm     0   0 %
#>  no_slope     0   0 %
# View(conc_flags)
flux_plot(
    conc_flags,
    f_conc = CO2_dry,
    print_plot = FALSE,
    output = "pdfpages",
    scale_x_datetime_args = list(
        date_breaks = "10 min",
        minor_breaks = "2 min",
        date_label = "%e/%m \n %H:%M"
    ),
    f_plotname = "eosense4h10f",
    f_ylim_upper = 650
    )

Note: I found a bug in flux_calc, it is not dealing well with the pressure as a variable. It is fixed in fluxible v1.2.5 (dev version), if you are using the CRAN version (v1.2.2) you will need to set the pressure as a constant.

fluxible_df <- flux_calc(
    slopes_df = conc_flags,
    slope_col = f_slope_corr,
    temp_air_col = air_temp,
    setup_volume = 72, # not sure if correct, found on website
    # setup_volume = 4.756,
    plot_area = 0.21, # to check
    # plot_area = 0.032,
    # atm_pressure = 1,
    atm_pressure = pressure,
    conc_unit = "ppm",
    flux_unit = "umol/m2/s",
    cols_keep = "f_quality_flag"
) |>
    rename(
        fluxible_flux = "f_flux",
        fluxible_slope = "f_slope_corr"
    ) |>
    mutate(
        f_datetime = f_datetime + 29329 # correcting the other way to match eosense
    )
#> Cutting data according to 'keep_arg'...
#> Averaging air temperature for each flux...
#> Creating a df with the columns from 'cols_keep' argument...
#> Calculating fluxes...
#> R constant set to 0.082057
#> Concentration was measured in ppm
#> Fluxes are in umol/m2/s
# View(fluxible_df)

saveRDS(fluxible_df, "compare_data/fluxible_df.rds")

Structure of fluxible_df:

#> tibble [55 × 8] (S3: tbl_df/tbl/data.frame)
#>  $ f_quality_flag    : chr [1:55] "discard" "ok" "ok" "ok" ...
#>  $ f_fluxid          : Factor w/ 113 levels "1","2","3","4",..: 45 4..
#>  $ fluxible_slope    : num [1:55] NA 0.023 0.0543 0.0564 0.0222 ...
#>  $ f_temp_air_ave    : num [1:55] 10.7 NaN 10.7 10.9 11 ...
#>  $ f_atm_pressure_ave: num [1:55] 0.993 NaN 1.048 0.992 1.052 ...
#>  $ f_datetime        : POSIXct[1:55], format: "2024-12-11 20:37:33" ..
#>  $ fluxible_flux     : num [1:55] NA NaN 0.838 0.822 0.343 ...
#>  $ f_model           : chr [1:55] "linear" "linear" "linear" "linea"..
#>  - attr(*, "fit_type")= chr "linear"