Skip to contents

Overview

classify_texture() provides two texture-classification paths:

  • USDA 12-class major texture classification from sand, silt, and clay percentages.
  • GRADISTAT-style rule classification for gravel-sand-mud textural groups and no-gravel sand-silt-clay mini texture classes.
  • Generic classification against user-supplied ternary texture polygons.

The USDA path is rule-based. It does not require bundled USDA polygon data, and no USDA polygon dataset is included as package data.

The GRADISTAT path is also rule-based. It uses source-grounded decision tables from the user-provided GRADISTAT v8 workbook and the textural-output context described by Blott and Pye (2001). Sediment-name composition is available as a separate step or optional output. GRADISTAT ternary plotting is available through plot_texture_ternary().

long_file <- system.file("extdata", "grain.long.csv", package = "grainsizeR")
wide_file <- system.file("extdata", "grain.wide.csv", package = "grainsizeR")

long_gs <- read_gsd(long_file)
wide_gs <- read_gsd(wide_file, format = "wide")

USDA Major Texture Classification

Use scheme = "usda" and method = "rules" for the validated USDA major-class workflow.

samples <- data.frame(
  sample_id = c("A", "B", "C"),
  sand = c(85, 40, 20),
  silt = c(10, 40, 20),
  clay = c(5, 20, 60)
)

classify_texture(samples, scheme = "usda", method = "rules")
#> # A tibble: 3 × 11
#>   sample_id  sand  silt  clay texture_class_id texture_class
#>   <chr>     <dbl> <dbl> <dbl> <chr>            <chr>        
#> 1 A            85    10     5 loamy_sand       loamy sand   
#> 2 B            40    40    20 loam             loam         
#> 3 C            20    20    60 clay             clay         
#> # ℹ 5 more variables: classification_method <chr>, rule_status <chr>,
#> #   all_rule_matches <chr>, rule_conflict <lgl>, rule_gap <lgl>

USDA texture ternary plotting is available through plot_texture_ternary(). The stable plot_texture_triangle() function name remains available for compatibility. The bundled long-format example includes finer fractions and can be used for the USDA texture workflow. Some open-ended tails require explicit extrapolation for resolved sand, silt, and clay percentages; this example makes that assumption visible. USDA ternary plots draw the 12 major-class boundaries from the package’s independently expressed rule logic and annotate ternary percentage axes directly, without ordinary Cartesian x/y tick labels. USDA ternary plots place percent sand, percent silt, and percent clay outside the corresponding triangle sides. Samples whose finest measured boundary is too far from USDA’s 2 um clay threshold for the extrapolation to be reliable are excluded rather than shown as a silent guess; a small extrapolation-driven overshoot past the natural 0% clay boundary is clipped to 0 and the difference proportionally redistributed across sand/silt so percentages still sum to exactly 100.

usda_fractions <- suppressWarnings(gs_fractions_wide(
  long_gs,
  scheme = "usda",
  normalize = "fine_earth",
  extrapolate = "warn_linear"
))

usda_components <- c("sand_percent", "silt_percent", "clay_percent")
usda_matrix <- as.matrix(usda_fractions[usda_components])
usda_clipped <- pmax(usda_matrix, 0)
usda_clip_correction <- rowSums(usda_matrix) - rowSums(usda_clipped)
usda_keep <- stats::complete.cases(usda_matrix) &
  apply(usda_matrix, 1, function(row) all(is.finite(row))) &
  (-usda_clip_correction) <= 10
usda_fractions[usda_components] <- usda_clipped / rowSums(usda_clipped) * 100
usda_fractions <- usda_fractions[usda_keep, ]

head(classify_texture(usda_fractions, scheme = "usda", method = "rules"))
#> # A tibble: 6 × 11
#>   sample_id  sand  silt  clay texture_class_id texture_class
#>   <chr>     <dbl> <dbl> <dbl> <chr>            <chr>        
#> 1 S01        86.4 13.6      0 sand             sand         
#> 2 S04        86.4 13.6      0 sand             sand         
#> 3 S05        90.7  9.29     0 sand             sand         
#> 4 S19        83.5 16.5      0 loamy_sand       loamy sand   
#> 5 S20        86.2 13.8      0 sand             sand         
#> 6 S21        82.4 17.6      0 loamy_sand       loamy sand   
#> # ℹ 5 more variables: classification_method <chr>, rule_status <chr>,
#> #   all_rule_matches <chr>, rule_conflict <lgl>, rule_gap <lgl>

plot_texture_ternary(usda_fractions, scheme = "usda", labels = FALSE)

Input percentages must be numeric, finite, between 0 and 100, and sum to approximately 100. classify_texture() does not silently normalize invalid sand, silt, and clay sums.

Data frames with ternary left, right, and top columns are also accepted for USDA rules. They are interpreted as left = sand, right = silt, and top = clay.

Method Selection

method = "auto" chooses the USDA rule path when scheme = "usda" and no texture polygons are supplied. It also chooses GRADISTAT rules when scheme = "gradistat" and no texture polygons are supplied.

classify_texture(samples, scheme = "usda", method = "auto")
#> # A tibble: 3 × 11
#>   sample_id  sand  silt  clay texture_class_id texture_class
#>   <chr>     <dbl> <dbl> <dbl> <chr>            <chr>        
#> 1 A            85    10     5 loamy_sand       loamy sand   
#> 2 B            40    40    20 loam             loam         
#> 3 C            20    20    60 clay             clay         
#> # ℹ 5 more variables: classification_method <chr>, rule_status <chr>,
#> #   all_rule_matches <chr>, rule_conflict <lgl>, rule_gap <lgl>

method = "rules" supports scheme = "usda" and scheme = "gradistat". Other schemes require user-supplied polygons because grainsizeR does not bundle built-in texture polygon datasets yet.

method = "polygon" uses the generic user-supplied polygon workflow.

GRADISTAT Texture Classification

Use scheme = "gradistat" and method = "rules" for GRADISTAT-style texture classification. The gravel_sand_mud basis accepts gravel, sand, and mud percentage columns, official gravel_sand_mud fraction output, or official gradistat fraction output where silt and clay are aggregated to mud. The bundled dry-sieve wide example supports this GRADISTAT-style gravel-sand-mud workflow.

wide_gradistat <- suppressWarnings(gs_fractions_wide(wide_gs, scheme = "gravel_sand_mud"))
wide_gradistat <- wide_gradistat[
  stats::complete.cases(wide_gradistat[c("gravel_percent", "sand_percent", "mud_percent")]),
]

classify_texture(
  head(wide_gradistat, 6),
  scheme = "gradistat",
  method = "rules",
  basis = "gravel_sand_mud",
  include_sediment_name = TRUE
)
#> # A tibble: 6 × 21
#>   sample_id gravel  sand    mud texture_class_id     texture_class ternary_basis
#>   <chr>      <dbl> <dbl>  <dbl> <chr>                <chr>         <chr>        
#> 1 S01        0.624  85.0 14.4   slightly_gravelly_m… slightly gra… gravel_sand_…
#> 2 S02        0.224  97.8  1.93  slightly_gravelly_s… slightly gra… gravel_sand_…
#> 3 S03        0.312  95.1  4.60  slightly_gravelly_s… slightly gra… gravel_sand_…
#> 4 S04        0.153  89.6 10.2   slightly_gravelly_m… slightly gra… gravel_sand_…
#> 5 S05        0.295  88.8 10.9   slightly_gravelly_m… slightly gra… gravel_sand_…
#> 6 S06        0.230  98.8  0.964 slightly_gravelly_s… slightly gra… gravel_sand_…
#> # ℹ 14 more variables: classification_method <chr>,
#> #   classification_status <chr>, notes <chr>, sand_mud_ratio <dbl>,
#> #   textural_group_class_id <chr>, textural_group <chr>,
#> #   mini_texture_class_id <chr>, mini_texture_class <chr>,
#> #   dominant_gravel_class <chr>, dominant_sand_class <chr>,
#> #   dominant_silt_class <chr>, sediment_name <chr>, sediment_name_status <chr>,
#> #   sediment_name_method <chr>

The sand_silt_clay_no_gravel basis requires sand, silt, and clay percentage columns.

ssc <- data.frame(
  sample_id = c("A", "B", "C"),
  sand = c(95, 60, 20),
  silt = c(3, 30, 60),
  clay = c(2, 10, 20)
)

classify_texture(
  ssc,
  scheme = "gradistat",
  method = "rules",
  basis = "sand_silt_clay_no_gravel"
)
#> # A tibble: 3 × 11
#>   sample_id  sand  silt  clay texture_class_id texture_class ternary_basis      
#>   <chr>     <dbl> <dbl> <dbl> <chr>            <chr>         <chr>              
#> 1 A            95     3     2 sand             sand          sand_silt_clay_no_…
#> 2 B            60    30    10 silty_sand       silty sand    sand_silt_clay_no_…
#> 3 C            20    60    20 sandy_silt       sandy silt    sand_silt_clay_no_…
#> # ℹ 4 more variables: classification_method <chr>, classification_status <chr>,
#> #   notes <chr>, silt_clay_ratio <dbl>

GRADISTAT rule classification returns texture_class_id, texture_class, classification_method, classification_status, ternary_basis, notes, and a ratio audit column. Input percentages must sum to approximately 100 for the selected basis; grainsizeR does not silently normalize invalid sums.

TEXTURAL GROUP and SEDIMENT NAME are distinct GRADISTAT-style outputs. Sediment-name composition can be requested during GRADISTAT classification with include_sediment_name = TRUE, or added afterward with gs_gradistat_sediment_name(). If dominant size-subclass columns are missing, the textural group is returned as a partial sediment name.

The same supported bases can be drawn as texture ternary plots. For the dry-sieve wide example, sample labels are suppressed to keep the GRADISTAT gravel-sand-mud class labels readable. In the gravel-sand-mud basis, the ternary plot places Gravel at the top, Mud at the lower-left apex, and Sand at the lower-right apex. Gravel percentage guides and sand/mud ratio guides are drawn as ternary annotations rather than Cartesian axes; unlike the USDA display, these GRADISTAT apex labels remain at the vertices.

plot_texture_ternary(
  head(wide_gradistat, 6),
  scheme = "gradistat",
  basis = "gravel_sand_mud",
  point_id = "sample_id",
  show_sample_labels = FALSE
)


plot_texture_ternary(
  ssc,
  scheme = "gradistat",
  basis = "sand_silt_clay_no_gravel",
  point_id = "sample_id"
)

Distribution, cumulative, fraction, USDA texture ternary, and GRADISTAT texture ternary plots are R-native functional replacements, not Excel visual clones.

plot_texture_ternary() is the preferred plotting name in new examples. plot_texture_triangle() remains available as an equivalent compatibility alias, while plot_trigon() is retained for legacy raw-data ternary workflows.

Output Columns

USDA rule classification returns the input rows with these classification columns appended:

  • texture_class_id
  • texture_class
  • classification_method
  • rule_status
  • all_rule_matches
  • rule_conflict
  • rule_gap

For valid, clean USDA inputs, classification_method is "usda_major_rules" and rule_status is "classified".

Polygon classification also reports public class labels with texture_class_id and texture_class, while retaining polygon-specific component, coordinate, and status columns such as left, right, top, x, y, resolved, and ambiguous.

User-Supplied Polygon Classification

Generic polygon classification remains available for user-provided texture systems. The example below defines a synthetic full ternary region for demonstration only; it is not an official texture system.

polygons <- data.frame(
  scheme = "synthetic_ternary",
  class_id = "all",
  class_name = "Synthetic full ternary area",
  vertex_id = 1:3,
  left = c(100, 0, 0),
  right = c(0, 100, 0),
  top = c(0, 0, 100),
  left_component = "sand",
  right_component = "silt",
  top_component = "clay",
  reference_id = NA_character_,
  reference = NA_character_
)

polygons <- validate_texture_polygons(polygons)
synthetic <- data.frame(
  sample_id = rep("A", 4),
  size_mm = c(2, 0.05, 0.002, 0.001),
  retained = c(10, 40, 30, 20)
)

synthetic_gs <- as_gsd_tbl(
  synthetic,
  sample_id,
  size_mm,
  retained,
  value_type = "percent"
)

classify_texture(
  synthetic_gs,
  texture_polygons = polygons,
  scheme = "synthetic_ternary",
  method = "polygon"
)
#> # A tibble: 1 × 13
#>   sample_id scheme  texture_class_id texture_class  left right   top     x     y
#>   <chr>     <chr>   <chr>            <chr>         <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 A         synthe… all              Synthetic fu…    40    30    20 0.444 0.192
#> # ℹ 4 more variables: resolved <lgl>, ambiguous <lgl>, normalize <chr>,
#> #   interpolation_scale <chr>

Limitations

The USDA workflow covers only the 12 major USDA textural classes. Sand-size modifier subclasses such as coarse sand, fine sand, very fine sand, coarse sandy loam, fine sandy loam, and very fine sandy loam are not implemented. They may be considered later as qualitative descriptor columns for D50 or particle-size summaries.

No USDA polygon dataset or USDA rule-data object is bundled as runtime package data.

GRADISTAT gravel-sand-mud and sand-silt-clay no-gravel ternary plotting is implemented through plot_texture_ternary(). Full Excel visual parity is not claimed.

CRAN readiness is not claimed in this vignette.

Provenance and Validation

The public USDA classification path uses an internal rule helper validated against USDA/NRCS validation examples and boundary cases. Public API tests cover representative official, targeted, and boundary points.

Performance notes are informational. The USDA rule classifier is vectorized and expected to be lighter than repeated polygon point-in-polygon checks for this built-in USDA path, but formal benchmarking is not included.

The public GRADISTAT classification path uses independently written R decision rules based on the documented GRADISTAT behavior. The workbook was used only as a reference implementation, and VBA source code was not copied.