Analysing vertical flame tests with textileFlame

textileFlame helps to validate, summarise, compare and report specimen-level results of vertical flame tests of textiles: afterflame time, afterglow time and char length, as obtained with methods such as ASTM D6413. It does not implement the test procedure and assumes no acceptance thresholds. Always consult the full edition of the method you used.

The data in this vignette are synthetic. They were generated to illustrate the package and do not represent any real material or laboratory.

Importing and validating data

Each row is one specimen. Besides the endpoints, the package needs the treatment and the identifier of the independently treated batch from which the specimen was cut.

raw <- read.csv(system.file("extdata", "flame_example.csv",
                            package = "textileFlame"))
head(raw[, c("specimen", "treatment", "batch", "direction", "laundering",
             "afterflame", "char_length", "char_length_status")])
#>    specimen treatment batch direction laundering afterflame char_length
#> 1  U1-wa0-1 Untreated    U1      warp          0       36.5         300
#> 2  U1-wa0-2 Untreated    U1      warp          0       33.8         300
#> 3  U1-wa0-3 Untreated    U1      warp          0       32.4         300
#> 4 U1-wa25-1 Untreated    U1      warp         25       35.2         300
#> 5 U1-wa25-2 Untreated    U1      warp         25       31.7         300
#> 6 U1-wa25-3 Untreated    U1      warp         25       25.6         300
#>   char_length_status
#> 1           censored
#> 2           censored
#> 3           censored
#> 4           censored
#> 5           censored
#> 6           censored

flame_data() checks the data (identifiers, negative or non-numeric values, treatment and batch labels, status values, duplicates) and converts all values to seconds and millimetres. Use validate_flame_data() to see every finding without stopping at the first error.

fd <- flame_data(raw, control = "Untreated",
                 method_edition = "synthetic example")
fd
#> <flame_data>  60 specimens, 5 batches, 3 treatments
#>   Treatments: Untreated, Plasma-A, Plasma-B
#>   Control:    Untreated
#>   Endpoints:  afterflame, afterglow, char_length (stored in s / mm)
#>   Strata:     direction, laundering
#>   Edition:    synthetic example
#>   afterglow: 0 censored, 1 missing
#>   char_length: 13 censored, 0 missing
#> 
#>    specimen treatment batch direction laundering afterflame afterflame_status
#> 1  U1-wa0-1 Untreated    U1      warp          0       36.5          measured
#> 2  U1-wa0-2 Untreated    U1      warp          0       33.8          measured
#> 3  U1-wa0-3 Untreated    U1      warp          0       32.4          measured
#> 4 U1-wa25-1 Untreated    U1      warp         25       35.2          measured
#> 5 U1-wa25-2 Untreated    U1      warp         25       31.7          measured
#> 6 U1-wa25-3 Untreated    U1      warp         25       25.6          measured
#>   afterglow afterglow_status char_length char_length_status  melt  drip
#> 1      24.7         measured         300           censored FALSE FALSE
#> 2      24.6         measured         300           censored FALSE FALSE
#> 3      20.9         measured         300           censored FALSE FALSE
#> 4      17.5         measured         300           censored FALSE FALSE
#> 5      21.8         measured         300           censored FALSE FALSE
#> 6      24.2         measured         300           censored FALSE FALSE
#>                                    notes
#> 1 specimen consumed over its full length
#> 2 specimen consumed over its full length
#> 3 specimen consumed over its full length
#> 4 specimen consumed over its full length
#> 5 specimen consumed over its full length
#> 6 specimen consumed over its full length
#> # ... with 54 more specimens

Missing and incomplete observations

Each endpoint can carry a status: measured, missing, censored or not_applicable. In this synthetic example, complete consumption is modelled as a lower-bound observation and explicitly marked censored. This is an analytical convention, not a rule prescribed by ASTM. Use it only when the recorded value is a valid lower bound on the quantity being analysed. Complete consumption by itself does not establish that interpretation; follow the method and laboratory reporting rules actually used. Censored values are never silently treated as zero or as ordinary measurements.

table(fd$treatment, fd$char_length_status)
#>            
#>             measured missing censored not_applicable
#>   Untreated        0       0       12              0
#>   Plasma-A        24       0        0              0
#>   Plasma-B        23       0        1              0

Summaries

Summaries are computed within strata (here direction and laundering) so that unlike measurements are not pooled by accident. When censored values are present, bound says that the statistics are lower bounds.

s <- summarize_flame(fd, endpoint = "char_length")
s[s$direction == "warp" & s$laundering == "0",
  c("treatment", "n_specimens", "n_batches", "n_censored", "mean", "bound")]
#>   treatment n_specimens n_batches n_censored      mean       bound
#> 1 Untreated           3         1          3 300.00000 lower_bound
#> 5  Plasma-A           6         2          0  87.33333       exact
#> 9  Plasma-B           6         2          0 133.50000       exact

Batches, not specimens, are the replicates

Strips cut from one treated fabric are not independent treatment replicates. By default compare_treatments() averages specimens within each batch and compares batch means with a Welch interval. When a group has fewer than two batches, as the single untreated batch here, the difference is still reported but no interval is computed, and a note says why.

cmp <- compare_treatments(fd, endpoint = "afterflame")
cmp[, c("treatment", "direction", "laundering", "n_treated", "n_control",
        "difference", "conf_low", "pct_reduction", "status")]
#>   treatment direction laundering n_treated n_control difference conf_low
#> 1  Plasma-A      warp          0         2         1  -32.56667       NA
#> 2  Plasma-B      warp          0         2         1  -30.40000       NA
#> 3  Plasma-A      warp         25         2         1  -28.93333       NA
#> 4  Plasma-B      warp         25         2         1  -25.11667       NA
#> 5  Plasma-A      weft          0         2         1  -28.21667       NA
#> 6  Plasma-B      weft          0         2         1  -25.28333       NA
#> 7  Plasma-A      weft         25         2         1  -31.28333       NA
#> 8  Plasma-B      weft         25         2         1  -28.28333       NA
#>   pct_reduction    status
#> 1      95.13145 estimated
#> 2      88.80234 estimated
#> 3      93.83784 estimated
#> 4      81.45946 estimated
#> 5      95.21935 estimated
#> 6      85.32058 estimated
#> 7      94.41650 estimated
#> 8      85.36217 estimated

Using unit = "specimen" does give intervals, but they only describe variation among strips within the available batches, and the function warns accordingly.

cmp_spec <- suppressWarnings(
  compare_treatments(fd, endpoint = "afterflame", unit = "specimen")
)
cmp_spec[cmp_spec$direction == "warp" & cmp_spec$laundering == "0",
         c("treatment", "difference", "conf_low", "conf_high",
           "pct_reduction", "pct_low", "pct_high")]
#>   treatment difference  conf_low conf_high pct_reduction  pct_low pct_high
#> 1  Plasma-A  -32.56667 -37.55914 -27.57420      95.13145 92.51478 97.54231
#> 2  Plasma-B  -30.40000 -34.76322 -26.03678      88.80234 83.37009 93.92415

Comparisons that involve censored observations

For char length, all untreated specimens were consumed. By default, such comparisons are not estimated:

cmp_char <- compare_treatments(fd, endpoint = "char_length")
head(cmp_char[, c("treatment", "direction", "laundering", "status")], 4)
#>   treatment direction laundering        status
#> 1  Plasma-A      warp          0 not_estimable
#> 2  Plasma-B      warp          0 not_estimable
#> 3  Plasma-A      warp         25 not_estimable
#> 4  Plasma-B      warp         25 not_estimable

With censored = "bound" the recorded values give a one-sided statement: the untreated control burned at least as far as recorded, so the true char-length reduction is at least the reported one. No interval or test is computed for bounds.

cmp_bound <- compare_treatments(fd, endpoint = "char_length",
                                censored = "bound")
cmp_bound[1:4, c("treatment", "direction", "laundering", "difference",
                 "diff_bound", "pct_reduction", "pct_bound", "status")]
#>   treatment direction laundering difference diff_bound pct_reduction pct_bound
#> 1  Plasma-A      warp          0  -212.6667      upper      70.88889     lower
#> 2  Plasma-B      warp          0  -166.5000      upper      55.50000     lower
#> 3  Plasma-A      warp         25  -171.3333      upper      57.11111     lower
#> 4  Plasma-B      warp         25  -130.5000      upper      43.50000     lower
#>      status
#> 1 estimated
#> 2 estimated
#> 3 estimated
#> 4 estimated

Acceptance criteria

The package never assumes thresholds. If a specification or contract gives limits, enter them together with their source. The values below are illustrative only.

crit <- flame_criteria(
  data.frame(endpoint = c("afterflame", "char_length"),
             statistic = c("mean", "individual"),
             max = c(5, 200)),
  source = "Illustrative values for this vignette only"
)
ev <- evaluate_criteria(fd, crit)
ev[ev$direction == "warp" & ev$laundering == "0",
   c("treatment", "endpoint", "statistic", "limit", "observed", "result")]
#>    treatment    endpoint  statistic limit   observed result
#> 1  Untreated  afterflame       mean     5  34.233333   fail
#> 2  Untreated char_length individual   200 300.000000   fail
#> 9   Plasma-A  afterflame       mean     5   1.666667   pass
#> 10  Plasma-A char_length individual   200 105.000000   pass
#> 17  Plasma-B  afterflame       mean     5   3.833333   pass
#> 18  Plasma-B char_length individual   200 142.000000   pass

A result is indeterminate when missing or censored observations prevent a decision.

Plots and reports

plot_specimens(fd, endpoint = "char_length")

plot_comparison(cmp)

flame_report() writes a plain Markdown file that records the units, method edition, handling of censored values and package version.

txt <- flame_report(fd, comparisons = cmp, criteria = crit,
                    date = as.Date("2026-01-01"))
head(txt, 10)
#>  [1] "# Vertical flame test report"                                          
#>  [2] ""                                                                      
#>  [3] "Generated: 2026-01-01  "                                               
#>  [4] "Package: textileFlame 0.1.0"                                           
#>  [5] ""                                                                      
#>  [6] "## Data and method"                                                    
#>  [7] ""                                                                      
#>  [8] "- Specimens: 60 from 5 treated batches"                                
#>  [9] "- Treatments (specimens): Untreated (12), Plasma-A (24), Plasma-B (24)"
#> [10] "- Control: Untreated"

Results describe performance under the specified laboratory exposure and do not by themselves establish real-world fire safety.

Statistical assumptions and references

Treatment comparisons are unpaired, between independent groups. They do not model paired batches, repeated measurements, or laboratory effects. Batch means receive equal weight even when different numbers of strips are tested. Approximate Welch intervals assume independent batch means and sufficiently appropriate sampling distributions; very small batch counts require care. Missing observations are omitted, so estimates are conditional on available observations. Percentage-reduction intervals use a Fieller construction with an approximate Welch quantile. Unbounded ratio intervals are returned as missing limits, not as evidence of a precisely estimated reduction.

When batch = NA, per-specimen identifiers are used for bookkeeping and default batch-level intervals and tests are disabled. Explicit specimen-level analyses require the user to justify independence.

Unit conversion is for analysis only. It does not certify that mixed SI and inch-pound test procedures conform to ASTM. No ASTM procedure or proprietary standard text is distributed with this package.