Author: Sadikul Islam
Date: 2026-03-19

# 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