Introduction to soiltillr

Sadikul Islam

2026-03-19

Overview

soiltillr provides a complete workflow for analysing soil tillage depth and erosion data across years and field treatments. It covers data validation, tillage summaries, compaction detection, erosion tracking, soil loss estimation, and visualisation.

This vignette walks through the full analysis pipeline using the two built-in datasets: tillage_operations and erosion_profile.

library(soiltillr)

1. Built-in datasets

data(tillage_operations)
data(erosion_profile)

head(tillage_operations)
#>   year       date field_id      operation depth_cm speed_kmh
#> 1 2018 2018-03-15  Field_A moldboard_plow       32       6.5
#> 2 2018 2018-10-20  Field_A    disc_harrow       12       8.0
#> 3 2019 2019-03-18  Field_A moldboard_plow       30       6.5
#> 4 2019 2019-10-22  Field_A    disc_harrow       11       8.0
#> 5 2020 2020-03-20  Field_A    chisel_plow       22       7.0
#> 6 2020 2020-10-18  Field_A    disc_harrow       10       8.5
head(erosion_profile)
#>   year field_id erosion_depth_mm soil_loss_t_ha bulk_density_g_cm3
#> 1 2018  Field_A              4.8           12.4               1.42
#> 2 2019  Field_A              4.5           11.8               1.45
#> 3 2020  Field_A              3.9           10.2               1.48
#> 4 2021  Field_A              3.2            8.5               1.44
#> 5 2022  Field_A              2.4            6.3               1.40
#> 6 2023  Field_A              1.8            4.7               1.36
#>   organic_matter_pct rainfall_mm slope_pct
#> 1                2.8         680       4.5
#> 2                2.6         720       4.5
#> 3                2.5         590       4.5
#> 4                2.6         810       4.5
#> 5                2.8         640       4.5
#> 6                3.1         700       4.5

tillage_operations contains 20 records of tillage events across two fields (Field_A and Field_B) from 2018 to 2023. Field_A transitions from conventional deep tillage (moldboard plowing at 30+ cm) to conservation tillage and no-till by 2023. Field_B maintains reduced tillage throughout.

erosion_profile contains annual soil erosion depth, soil loss, bulk density, organic matter, rainfall, and slope for the same two fields and period.


2. Data validation

Always validate your data before analysis. validate_soil_data() checks for missing columns, negative values, empty data frames, and implausible years.

chk <- validate_soil_data(
  data      = tillage_operations,
  year_col  = "year",
  field_col = "field_id",
  value_col = "depth_cm"
)

chk$valid    # TRUE = data is clean
#> [1] TRUE
chk$issues   # critical errors
#> character(0)
chk$warnings # informational notices
#> character(0)

3. Tillage analysis

3.1 Summarise tillage operations

summarise_tillage() computes year × field statistics: number of operations, mean, maximum, and total depth, plus dominant operation type.

till_sum <- summarise_tillage(
  data      = tillage_operations,
  year_col  = "year",
  field_col = "field_id",
  depth_col = "depth_cm",
  op_col    = "operation"
)
print(till_sum)
#>    year field_id n_operations mean_depth_cm max_depth_cm total_depth_cm
#> 1  2018  Field_A            2          22.0           32             44
#> 2  2018  Field_B            1          20.0           20             20
#> 3  2019  Field_A            2          20.5           30             41
#> 4  2019  Field_B            2          13.5           18             27
#> 5  2020  Field_A            2          16.0           22             32
#> 6  2020  Field_B            1          17.0           17             17
#> 7  2021  Field_A            2          15.0           20             30
#> 8  2021  Field_B            2           7.0           14             14
#> 9  2022  Field_A            2           9.0           18             18
#> 10 2022  Field_B            1          13.0           13             13
#> 11 2023  Field_A            2           7.5           15             15
#> 12 2023  Field_B            1           0.0            0              0
#>    dominant_operation
#> 1         disc_harrow
#> 2         chisel_plow
#> 3         disc_harrow
#> 4         chisel_plow
#> 5         chisel_plow
#> 6         chisel_plow
#> 7         chisel_plow
#> 8   conservation_till
#> 9         chisel_plow
#> 10  conservation_till
#> 11  conservation_till
#> 12            no_till

Field_A shows mean depth declining from 22.0 cm in 2018 to 7.5 cm in 2023, reflecting the shift toward conservation tillage.

3.2 Tillage depth trend

tillage_depth_trend() calculates the year-on-year change in mean depth per field and classifies each year as "baseline", "increasing", "decreasing", or "stable".

till_trend <- tillage_depth_trend(
  data      = tillage_operations,
  year_col  = "year",
  field_col = "field_id",
  depth_col = "depth_cm"
)
print(till_trend)
#>    year field_id mean_depth_cm depth_change_cm      trend
#> 1  2018  Field_A          22.0              NA   baseline
#> 2  2019  Field_A          20.5            -1.5 decreasing
#> 3  2020  Field_A          16.0            -4.5 decreasing
#> 4  2021  Field_A          15.0            -1.0 decreasing
#> 5  2022  Field_A           9.0            -6.0 decreasing
#> 6  2023  Field_A           7.5            -1.5 decreasing
#> 7  2018  Field_B          20.0              NA   baseline
#> 8  2019  Field_B          13.5            -6.5 decreasing
#> 9  2020  Field_B          17.0             3.5 increasing
#> 10 2021  Field_B           7.0           -10.0 decreasing
#> 11 2022  Field_B          13.0             6.0 increasing
#> 12 2023  Field_B           0.0           -13.0 decreasing

# Years with decreasing tillage depth
till_trend[till_trend$trend == "decreasing", ]
#>    year field_id mean_depth_cm depth_change_cm      trend
#> 2  2019  Field_A          20.5            -1.5 decreasing
#> 3  2020  Field_A          16.0            -4.5 decreasing
#> 4  2021  Field_A          15.0            -1.0 decreasing
#> 5  2022  Field_A           9.0            -6.0 decreasing
#> 6  2023  Field_A           7.5            -1.5 decreasing
#> 8  2019  Field_B          13.5            -6.5 decreasing
#> 10 2021  Field_B           7.0           -10.0 decreasing
#> 12 2023  Field_B           0.0           -13.0 decreasing

3.3 Compaction detection

detect_compaction() classifies compaction risk as "high", "moderate", or "low" based on a user-defined depth threshold (default 20 cm) and estimates the depth of potential plow pan formation.

risk <- detect_compaction(
  data                    = tillage_operations,
  year_col                = "year",
  field_col               = "field_id",
  depth_col               = "depth_cm",
  op_col                  = "operation",
  compaction_threshold_cm = 20
)
print(risk)
#>    year field_id mean_depth_cm compaction_risk plow_pan_depth_cm
#> 1  2018  Field_A          22.0            high              24.0
#> 2  2018  Field_B          20.0            high              22.0
#> 3  2019  Field_A          20.5            high              22.5
#> 4  2019  Field_B          13.5             low                NA
#> 5  2020  Field_A          16.0        moderate              18.0
#> 6  2020  Field_B          17.0        moderate              19.0
#> 7  2021  Field_A          15.0        moderate              17.0
#> 8  2021  Field_B           7.0             low                NA
#> 9  2022  Field_A           9.0             low                NA
#> 10 2022  Field_B          13.0             low                NA
#> 11 2023  Field_A           7.5             low                NA
#> 12 2023  Field_B           0.0             low                NA

# High-risk years only
risk[risk$compaction_risk == "high", ]
#>   year field_id mean_depth_cm compaction_risk plow_pan_depth_cm
#> 1 2018  Field_A          22.0            high              24.0
#> 2 2018  Field_B          20.0            high              22.0
#> 3 2019  Field_A          20.5            high              22.5

4. Erosion analysis

4.1 Track erosion depth

track_erosion_depth() computes annual erosion depth, year-on-year change, and cumulative erosion per field. Trend is classified as "baseline", "improving", "worsening", or "stable".

eros <- track_erosion_depth(
  data        = erosion_profile,
  year_col    = "year",
  field_col   = "field_id",
  erosion_col = "erosion_depth_mm"
)
print(eros)
#>    year field_id erosion_depth_mm change_mm cumulative_loss_mm     trend
#> 1  2018  Field_A              4.8        NA                4.8  baseline
#> 2  2019  Field_A              4.5      -0.3                9.3 improving
#> 3  2020  Field_A              3.9      -0.6               13.2 improving
#> 4  2021  Field_A              3.2      -0.7               16.4 improving
#> 5  2022  Field_A              2.4      -0.8               18.8 improving
#> 6  2023  Field_A              1.8      -0.6               20.6 improving
#> 7  2018  Field_B              2.9        NA                2.9  baseline
#> 8  2019  Field_B              2.6      -0.3                5.5 improving
#> 9  2020  Field_B              2.3      -0.3                7.8 improving
#> 10 2021  Field_B              2.0      -0.3                9.8 improving
#> 11 2022  Field_B              1.6      -0.4               11.4 improving
#> 12 2023  Field_B              1.3      -0.3               12.7 improving

Both fields show consistently improving erosion trends. Field_A’s cumulative loss (20.6 mm) is higher than Field_B’s (12.7 mm), reflecting its historically more intensive tillage.

4.2 Estimate soil loss

estimate_soil_loss() uses a mass-balance approach to estimate soil loss in t/ha. The optional slope correction applies the McCool et al. (1987) simplified LS factor: LS = (slope% / 9)^0.6.

loss <- estimate_soil_loss(
  data             = erosion_profile,
  year_col         = "year",
  field_col        = "field_id",
  erosion_col      = "erosion_depth_mm",
  bulk_density_col = "bulk_density_g_cm3",
  slope_col        = "slope_pct"
)
print(loss)
#>    year field_id erosion_depth_mm estimated_loss_t_ha loss_category
#> 1  2018  Field_A              4.8               71.23   very severe
#> 2  2019  Field_A              4.5               68.19   very severe
#> 3  2020  Field_A              3.9               60.32   very severe
#> 4  2021  Field_A              3.2               48.15   very severe
#> 5  2022  Field_A              2.4               35.11   very severe
#> 6  2023  Field_A              1.8               25.58   very severe
#> 7  2018  Field_B              2.9               40.25   very severe
#> 8  2019  Field_B              2.6               35.82   very severe
#> 9  2020  Field_B              2.3               31.45   very severe
#> 10 2021  Field_B              2.0               27.14   very severe
#> 11 2022  Field_B              1.6               21.38   very severe
#> 12 2023  Field_B              1.3               17.11        severe

Note: estimated_loss_t_ha is a physics-based estimate from erosion depth and bulk density. It will differ from independently measured soil_loss_t_ha values in the dataset. For full RUSLE prediction, all five factors (R, K, LS, C, P) should be considered (Renard et al., 1997).

4.3 Compare fields

compare_fields() produces a wide-format year-by-year comparison showing erosion depth and organic matter for each field, plus difference columns.

comp <- compare_fields(
  data        = erosion_profile,
  year_col    = "year",
  field_col   = "field_id",
  erosion_col = "erosion_depth_mm",
  om_col      = "organic_matter_pct"
)
print(comp)
#>   year erosion_Field_A_mm om_Field_A_pct erosion_Field_B_mm om_Field_B_pct
#> 1 2018                4.8            2.8                2.9            3.2
#> 2 2019                4.5            2.6                2.6            3.3
#> 3 2020                3.9            2.5                2.3            3.5
#> 4 2021                3.2            2.6                2.0            3.7
#> 5 2022                2.4            2.8                1.6            3.9
#> 6 2023                1.8            3.1                1.3            4.2
#>   erosion_diff_mm om_diff_pct
#> 1             1.9        -0.4
#> 2             1.9        -0.7
#> 3             1.6        -1.0
#> 4             1.2        -1.1
#> 5             0.8        -1.1
#> 6             0.5        -1.1

The erosion_diff_mm column confirms Field_A consistently has higher erosion than Field_B, with the gap narrowing from 1.9 mm in 2018 to 0.5 mm in 2023. The om_diff_pct column shows Field_B maintaining higher organic matter throughout, with Field_A recovering under conservation management.


5. Visualisation

5.1 Tillage depth timeline

plot_tillage_timeline(
  data      = tillage_operations,
  year_col  = "year",
  field_col = "field_id",
  depth_col = "depth_cm",
  title     = "Tillage Depth Transition: Conventional to Conservation"
)

Mean annual tillage depth by field, 2018-2023.

5.2 Erosion depth trend

plot_erosion_trend(
  data            = erosion_profile,
  year_col        = "year",
  field_col       = "field_id",
  erosion_col     = "erosion_depth_mm",
  show_cumulative = TRUE,
  title           = "Erosion Depth and Cumulative Loss 2018-2023"
)

Annual erosion depth and cumulative loss by field.

5.3 Organic matter trend

plot_om_trend(
  data      = erosion_profile,
  year_col  = "year",
  field_col = "field_id",
  om_col    = "organic_matter_pct",
  title     = "Soil Organic Matter Recovery"
)

Soil organic matter recovery under conservation tillage.

The dashed line at 3.5% represents a commonly cited threshold for adequate soil organic matter in agricultural soils. Field_A crosses this threshold by 2023 under conservation management.

5.4 Tillage vs erosion comparison

plot_tillage_erosion(
  tillage_data = tillage_operations,
  erosion_data = erosion_profile,
  year_col     = "year",
  field_col    = "field_id",
  depth_col    = "depth_cm",
  erosion_col  = "erosion_depth_mm",
  title        = "Impact of Tillage Management on Soil Erosion"
)

Dual-panel comparison of mean tillage depth and erosion depth.


6. Full analysis pipeline

The functions are designed to chain together naturally:

library(soiltillr)
data(tillage_operations)
data(erosion_profile)

# Step 1: validate
stopifnot(validate_soil_data(tillage_operations,
                             "year", "field_id", "depth_cm")$valid)

# Step 2: tillage
till_sum   <- summarise_tillage(tillage_operations, "year", "field_id",
                                "depth_cm", op_col = "operation")
till_trend <- tillage_depth_trend(tillage_operations, "year",
                                  "field_id", "depth_cm")
compact    <- detect_compaction(tillage_operations, "year", "field_id",
                                "depth_cm", op_col = "operation")

# Step 3: erosion
eros_track <- track_erosion_depth(erosion_profile, "year",
                                  "field_id", "erosion_depth_mm")
eros_loss  <- estimate_soil_loss(erosion_profile, "year", "field_id",
                                 "erosion_depth_mm", "bulk_density_g_cm3",
                                 slope_col = "slope_pct")
field_comp <- compare_fields(erosion_profile, "year", "field_id",
                              "erosion_depth_mm", "organic_matter_pct")

# Step 4: visualise
plot_tillage_timeline(tillage_operations, "year", "field_id", "depth_cm")
plot_erosion_trend(erosion_profile, "year", "field_id",
                   "erosion_depth_mm", show_cumulative = TRUE)
plot_om_trend(erosion_profile, "year", "field_id", "organic_matter_pct")
plot_tillage_erosion(tillage_operations, erosion_profile,
                     "year", "field_id", "depth_cm", "erosion_depth_mm")

References

Lal, R. (2001). Soil degradation by erosion. Land Degradation and Development, 12(6), 519–539. https://doi.org/10.1002/ldr.472

McCool, D. K., Brown, L. C., Foster, G. R., Mutchler, C. K., & Meyer, L. D. (1987). Revised slope steepness factor for the Universal Soil Loss Equation. Transactions of the ASAE, 30(5), 1387–1396. https://doi.org/10.13031/2013.30576

Renard, K. G., Foster, G. R., Weesies, G. A., McCool, D. K., & Yoder, D. C. (1997). Predicting Soil Erosion by Water: A Guide to Conservation Planning with the Revised Universal Soil Loss Equation (RUSLE). USDA Agriculture Handbook No. 703. https://ntrl.ntis.gov/NTRL/dashboard/searchResults/titleDetail/PB97153704.xhtml