7 Mistakes in LB Unit Conversion That Still Show Up in SDTM

7 Mistakes in LB Unit Conversion That Still Show Up in SDTM | StudySAS
StudySAS Blog | SDTM Programming | Regulatory Submissions

April 2026  |  LBORRES · LBSTRESC · LBSTRESN · FDA · PMDA · SAS

LB unit conversion is not hard to understand. The variable definitions are clear. The model is well-documented. And yet unit-related problems remain among the most common issues in SDTM submissions — not because teams do not know the rules, but because the failures are quiet. Nothing crashes. The dataset looks valid. Pinnacle 21 returns a clean report. Then a reviewer runs a simple cross-tabulation and finds creatinine flagged HIGH at a value that belongs well within the normal range — because the reference range was never converted to match the standard unit.

This post is a close look at seven implementation mistakes, each with enough context and worked examples to make the failure mode visible before it reaches a submission.

LBORRES is what was collected. Verbatim. Untouched.
LBSTRES* is the standardized representation. One unit per test.
LBSTNR* must follow the same unit system as LBSTRES*.

That three-line rule explains most of what goes wrong. The seven mistakes below are elaborations of what happens when any part of that contract is broken.


Background: what the standard requires

SDTMIG defines the LB domain variable roles clearly. LBORRES holds the result exactly as collected from the source. LBORRESU holds the unit as reported by the lab. LBSTRESC holds the standardized result in character form. LBSTRESN holds the numeric portion of that standardized result. LBSTRESU holds the standard unit — and this unit must be consistent for a given LBTESTCD across the entire dataset. LBSTNRLO and LBSTNRHI hold the standardized reference range limits, which must be in the same unit as LBSTRESU. LBNRIND holds the normal/abnormal flag, and SDTMIG explicitly requires that sponsors document whether this flag is derived from original ranges or standard ranges.

FDA's Study Data Technical Conformance Guide v6.1 (December 2025) adds a structural requirement on top of this: for clinical submissions, the LB domain should carry SI-standardized results, and a parallel custom domain LC should carry conventional-unit results. These are not alternative approaches — they are parallel datasets required in the same submission package. PMDA's guidance reinforces SI use in the standardized result fields and additionally requires that the conversion equation be documented in reviewer-facing materials when original and converted values coexist.

That is the framework. Now, where it breaks.


01 Changing LBORRES

This is the foundational error, and it is more common than it should be. LBORRES is the traceability anchor for the entire LB record. Its purpose is to preserve what the site or lab actually reported, in the exact form it was reported. The moment you alter LBORRES, you have introduced a gap between the submitted dataset and the source document — and that gap is very difficult to justify under audit.

The alterations I see most often are trimming trailing zeros (1.20 becomes 1.2), standardizing case (POSITIVE becomes Positive), removing comparison operators (<0.10 becomes 0.10), and normalizing text qualifiers (Below LOQ becomes BLQ). Each of these looks like a cleanup. None of them is permitted.

ScenarioSource valueLBORRES — wrongLBORRES — correct
Trailing zero trimmed 1.20 mg/dL 1.2 1.20
Operator stripped <0.10 ng/mL 0.10 <0.10
Text normalized Below LOQ BLQ Below LOQ
Case changed NEGATIVE Negative NEGATIVE

The right place for cleanup is LBSTRESC. If the sponsor has a standard for how qualitative results should be represented, that standard is applied in the standardized layer — not by modifying the original. LBORRES is read-only from the moment data is collected.

Regulatory implication: FDA expects that submitted data can be traced back to the source CRF or lab report. If LBORRES has been altered from the source value, the auditor's reconciliation path is broken. This is not a conformance warning — it is a data integrity issue.

02 Using LBSTRESC as a cosmetic copy of LBORRES

LBSTRESC exists to do real work: converting units, standardizing result formats, or mapping to a controlled representation. When a team populates LBSTRESC merely by reformatting LBORRES — trimming a zero, changing 1.20 to 1.2, or left-justifying a string — without any underlying standardization rule, the column exists but contributes nothing.

The problem goes beyond cosmetics. A dataset where LBSTRESC is a visual cleanup of LBORRES looks standardized to a tool like Pinnacle 21 — all the required variables are populated. But a reviewer who pulls LBSTRESN expecting a consistent, analysis-ready numeric result will find that the values are in whatever unit each site happened to report, because no actual standardization happened.

This mistake is hardest to detect in single-site studies where the lab reports all results in one unit. Everything looks fine because conversion was never needed. The hidden problem surfaces when the same mapping spec is reused in a multi-site follow-on study with a different lab, and the team assumes the spec is handling standardization when it is only handling formatting.

The test: for every LB record, ask whether LBSTRESC would have a different value if LBORRES were in a different unit. If the answer is no — if the logic does not touch the unit at all — then LBSTRESC is not doing standardization work. It is doing formatting work. That belongs in output displays, not in the SDTM submission dataset.

03 Converting the result but not the reference range

This is the most dangerous mistake on the list because it is completely silent in conformance validation and produces flags that look correct but are not. The result moves into the standard unit. The reference range stays in the original unit. LBNRIND is derived by comparing the converted result against the unconverted range. The comparison is numerically meaningless, but the output looks like a legitimate flag.

Here is a concrete worked example. A study collects glucose from US sites in mg/dL and from European sites in mmol/L. The sponsor selects mmol/L as the standard unit. The JCTLM-recommended conversion factor for mg/dL to mmol/L is division by 18.018.

VariableUS site record (collected in mg/dL)
LBORRES95
LBORRESUmg/dL
LBSTRESN5.27  (95 ÷ 18.018)
LBSTRESUmmol/L
LBNRLO as reported by lab70 mg/dL
LBNRHI as reported by lab100 mg/dL
LBSTNRLO — wrong70  (not converted)
LBSTNRHI — wrong100  (not converted)
LBNRIND derivedLOW  (5.27 < 70 in the comparison)

A glucose of 5.27 mmol/L is squarely within the normal range (approximately 3.9–6.1 mmol/L). But the range stored in LBSTNRLO is 70 and the stored unit is mmol/L — producing a comparison of 5.27 against 70, which flags the record as LOW. That flag travels into every safety table and shift table built from LBNRIND. Nothing in P21 catches it.

The correct derived ranges for this record are 70 ÷ 18.018 = 3.88 mmol/L and 100 ÷ 18.018 = 5.55 mmol/L. The conversion factor used for LBSTNRLO and LBSTNRHI must be identical to the one used for LBSTRESN. Same metadata table, same factor, same rounding rule.

VariableWrongCorrect
LBSTRESN5.275.27
LBSTRESUmmol/Lmmol/L
LBSTNRLO70  (mg/dL scale, unconverted)3.88  (mmol/L, converted)
LBSTNRHI100  (mg/dL scale, unconverted)5.55  (mmol/L, converted)
LBNRINDLOW  (wrong)NORMAL  (correct)

Important: The value 5.27 is correct in both cases. The failure is not the numeric result — it is that the reference ranges were not converted to match the standard unit. This breaks LBNRIND even when the result itself is correct.

SDTMIG also requires that sponsors document in Define.xml comments whether LBNRIND is based on original ranges or standardized ranges. This is explicit guidance, not optional. If you derive LBNRIND from standardized ranges — which is the cleaner and more consistent approach — state that. If you derive from original lab ranges, state that too. I have reviewed submissions where 20–30% of LBNRIND flags were wrong for exactly the reason shown above. Nothing in P21 caught it. A reviewer running basic summary statistics was the one who found it.

Check to run: for every record where LBORRESU ≠ LBSTRESU, verify that LBSTNRLO and LBSTNRHI are numerically consistent with the standard unit — not the original unit. If your LBSTRESN values are in the range 3–7 but your LBSTNRHI values are in the range 70–110, you have unconverted reference ranges.

04 Forcing qualitative results into LBSTRESN

SDTMIG is unambiguous: LBSTRESN holds the numeric portion of the standardized result. When the standardized result is qualitative — POSITIVE, NEGATIVE, NORMAL, ABNORMAL, TRACE, 1+, 2+, 3+ — there is no numeric portion. LBSTRESN must be null. The result goes into LBSTRESC in character form and stays there.

The pressure to encode ordinal results numerically in SDTM usually comes from analysts who want to sort or compute on the scale downstream. That is a legitimate need, but it belongs in ADaM — typically as a derived numeric variable in ADLB — not in the SDTM submission dataset. Populating LBSTRESN with 0 for NEGATIVE and 1 for POSITIVE, or with 0.5 for TRACE and 1 for 1+ and 2 for 2+, introduces a numeric encoding that SDTMIG does not support and that Pinnacle 21 will flag as SD0086 or similar depending on the ruleset.

LBORRESLBSTRESCLBSTRESN — wrongLBSTRESN — correct
NEGATIVENEGATIVE0. (null)
TRACETRACE0.5. (null)
1+1+1. (null)
2+2+2. (null)
3+3+3. (null)

The exception is a BLQ result that includes a numeric boundary. A result reported as <0.10 ng/mL is not purely qualitative — it has a numeric component. In this case, the boundary value belongs in LBSTRESN and the operator stays in LBSTRESC. That is the inequality pattern, covered in Mistake 5. The key distinction is whether the result has a numeric boundary at all. If LBORRES is the string BLQ with no numeric component attached, LBSTRESN is null. If LBORRES is <0.10, the boundary 0.10 goes into LBSTRESN.

/* Qualitative — LBSTRESN must be null */
LBORRES   = NEGATIVE
LBSTRESC  = NEGATIVE
LBSTRESN  = .

/* Ordinal — LBSTRESN must be null */
LBORRES   = 2+
LBSTRESC  = 2+
LBSTRESN  = .

/* BLQ with numeric boundary — inequality pattern applies */
LBORRES   = <0.10
LBSTRESC  = <0.10
LBSTRESN  = 0.10
LBSTRESU  = ng/mL

05 Losing the operator on inequality results

When a lab returns a result as <0.10, that full string is the result. The operator is not decoration — it is part of the scientific meaning. A value of <0.10 says the measurement was below the limit of detection and the true value is somewhere below the numeric boundary. Stripping the operator and storing 0.10 in LBSTRESC converts an undetected result into a detected one. That changes the data.

The SDTMIG model for inequality results is: the operator-qualified string goes into LBSTRESC, and the numeric boundary goes into LBSTRESN. When unit conversion is also needed, the conversion applies to the numeric boundary — and the converted operator string goes into LBSTRESC. Both variables must reflect the post-conversion boundary, not the original.

Here is a full worked example. Glucose is reported as <2.0 mmol/L — below the limit of detection for the assay. The sponsor standard unit is mg/dL, using a conversion factor of 18.018.

VariableWrongCorrectNote
LBORRES <2.0 <2.0 Verbatim — same in both cases
LBORRESU mmol/L mmol/L Original unit — same in both cases
LBSTRESC 36.04 <36.04 Operator dropped in wrong version
LBSTRESN 36.04 36.04 Numeric boundary — same in both
LBSTRESU mg/dL mg/dL Standard unit — same in both cases

The wrong version stores the converted numeric value in LBSTRESC without the operator. To a reviewer reading that record, glucose is 36.04 mg/dL — a detected value near the low end of the normal range, not a below-detection result. That is a materially different clinical statement.

A second failure mode is when the conversion is applied to the operator string correctly, but LBSTRESC and LBSTRESN are derived from two independent computations rather than one, causing a floating-point mismatch. The safe pattern is to compute the rounded LBSTRESN first, then derive LBSTRESC from that rounded value:

/* Compute once, derive twice — safe pattern */
lbstresn = round(2.0 * 18.018, 0.01);        /* = 36.04 */
lbstresc = cats('<', strip(put(lbstresn, best12.)));  /* = "<36.04" */

/* Independent computation — unsafe pattern */
/* lbstresc = cats('<', put(2.0 * 18.018, best12.)); */
/* lbstresn = round(2.0 * 18.018, 0.01);              */
/* These can diverge due to floating-point representation */
Edge case: some lab systems encode above-range results as >5000 and some as the plain value 5000 with a separate high flag. If LBORRES contains 5000 without an operator, LBSTRESC and LBSTRESN both take the value 5000. Do not infer an operator from an associated flag variable — only use an operator in LBSTRESC when it is present in the source result.

06 Allowing more than one LBSTRESU per LBTESTCD

The invariant is simple: for a given LBTESTCD, there is one standard unit. Every record for that test code — regardless of which site collected it, which lab analyzed it, or when it was collected — must have the same value in LBSTRESU. When that is violated, the standardized layer is not standardized. Analysis results computed across subjects become numerically mixed.

This mistake happens in three specific situations. The first is unit pass-through: the programmer assigns LBSTRESU directly from LBORRESU without going through a conversion metadata table, assuming all sites report in the same unit. When they do not, the assumption introduces the inconsistency silently. The second is a mid-study lab change: the central lab upgrades its reporting system and begins sending TSH in mIU/mL instead of µIU/mL. These units are numerically equivalent — the conversion factor is 1 — so no result values change, but the unit string in LBSTRESU now varies within the same test code. P21 does not flag this. The third is a new local lab joining mid-study with a unit that was not anticipated in the original mapping spec, and the programmer adds a conditional branch to handle it rather than updating the metadata table.

USUBJIDVISITLBTESTCDLBORRESLBORRESULBSTRESNLBSTRESU
US-001Week 4TSH2.1uIU/mL2.1uIU/mL
EU-007Week 4TSH2.1uIU/mL2.1uIU/mL
US-001Week 24TSH2.3mIU/mL2.3mIU/mL
EU-007Week 24TSH2.4mIU/mL2.4mIU/mL

The highlighted rows show Week 24 records where the lab changed its reporting unit string. The numeric values are correct — 1 mIU/mL equals 1 µIU/mL — but LBSTRESU now contains two different strings for the same LBTESTCD. Pinnacle 21 validates LBSTRESU against controlled terminology. It does not enforce a single unit per LBTESTCD.

The fix is architectural. LBSTRESU is assigned from a conversion metadata table keyed on LBTESTCD, not mapped directly from LBORRESU. The table says TSH → uIU/mL. It does not matter what string the lab sends in LBORRESU. The standard unit is always what the metadata says. When a unit change happens at the lab, the metadata table is updated through change control and the unit string is harmonized.

/* QC check: flag any LBTESTCD with multiple LBSTRESU values */
proc freq data=lb noprint;
  tables lbtestcd * lbstresu / out=unit_check;
run;

data unit_fail;
  set unit_check;
  by lbtestcd;
  if not (first.lbtestcd and last.lbtestcd);
run;

/* Any rows in unit_fail = submission-blocking issue */
proc print data=unit_fail; run;

07 Treating alternate units as a late-stage decision

This is the structural mistake. The others are implementation errors. This one is a planning failure that cannot be corrected cleanly at database lock.

For FDA clinical submissions, the current Study Data Technical Conformance Guide v6.1 (December 2025) is explicit: submit two lab domains. LB holds SI-standardized results in LBSTRESU, LBSTRESC, and LBSTRESN. LC is a custom domain structured identically to LB, carrying conventional-unit results in the corresponding –STRESU, –STRESC, and –STRESN fields. The guidance also states that the ideal source for both SI and conventional unit values is the lab vendor itself — not post-hoc conversion in SAS.

That last point is operationally significant. If the vendor does not provide both unit systems, you are computing the alternate unit yourself. That conversion has to be governed, documented, and validated. A post-hoc conversion that is not in the original study spec, not in Define.xml, and not reviewed by the QC programmer is not defensible under audit. The time to contract for dual-unit vendor output is during study design, not at lock.

ApproachStatusLB contentAlternate unit
SUPPLB workaround Outdated for FDA clinical Sponsor-chosen unit Buried in SUPPQUAL — poor reviewer access
Single domain, one unit Outdated for FDA clinical SI or conventional — one only Not submitted
LB + LC (SDTCG v6.1) Current requirement SI units in LBSTRESU Conventional units in parallel LC domain

LC is not a SUPPQUAL extension. It is a full parallel dataset with its own metadata in Define.xml, its own LCSEQ sequencing variable, and its own entry in the Study Data Reviewer's Guide. A team discovering at lock that FDA expects LC — and that the lab vendor was not contracted to provide conventional units — is looking at a programmatic conversion workstream, a Define.xml rewrite for an additional domain, and an SDRG update, all under submission timelines. That is avoidable with an early planning conversation.

PMDA's requirements differ in one important way. PMDA expects SI-standardized results in the submission and additionally requires that when original and converted values coexist in the dataset, the conversion equation must be documented in reviewer-facing materials — explicitly, not by reference. That means the SDRG or Define.xml Comments for LBSTRESN and LBSTRESC should state the factor used. "Converted from LBORRESU to LBSTRESU" is not enough. The equation or factor must be present.

Global submission design rule: if a study will be submitted to both FDA and PMDA, design around SI as the universal standard for LB from the start. Produce LC for conventional units to satisfy FDA's parallel-domain requirement. Document the conversion equation in Define.xml and the SDRG to satisfy PMDA. These two requirements are compatible — they just both need to be planned for.

What good implementation looks like

A defensible LB conversion setup rests on a single foundational design decision: conversion logic must live in a governed metadata table, not in SAS code. If your conversion logic lives in SAS code instead of a governed metadata table, you do not have a controlled process. You have an implementation. That distinction matters when a new local lab joins mid-enrollment, when a factor needs to be corrected, when a second programmer tries to independently reproduce the derivation, or when a regulatory reviewer asks how a specific result was computed.

The metadata table has one row per LBTESTCD and source unit. It contains the standard unit, the conversion factor, the source of that factor (JCTLM, NIST SP 811, or lab vendor specification), and the rounding precision. Every derivation — result, reference range, and flag — flows from the same table. When the table changes, every downstream derivation changes consistently. That is a controlled process.

/* Example conversion metadata — one row per LBTESTCD × source unit */
data lb_conv_meta;
  length lbtestcd $8  from_unit std_unit $40  source $60;
  input lbtestcd $ from_unit $ std_unit $ factor round_to source $;
  datalines;
CREAT   mg/dL    umol/L   88.4200  0.1   JCTLM
CREAT   umol/L   umol/L    1.0000  0.1   Pass-through
GLUC    mg/dL    mmol/L    0.0555  0.01  JCTLM
GLUC    mmol/L   mmol/L    1.0000  0.01  Pass-through
HGB     g/dL     g/L      10.0000  0.1   JCTLM
HGB     g/L      g/L       1.0000  0.1   Pass-through
TSH     uIU/mL   mIU/L     1.0000  0.001 Unit-equiv
TSH     mIU/mL   mIU/L     1.0000  0.001 Unit-equiv
;
run;

The derivation itself follows a fixed sequence: round LBSTRESN first, then derive LBSTRESC from the rounded value. Never derive LBSTRESC from the raw computation — floating-point residuals will produce character values that do not match LBSTRESN, generating a P21 finding. The same factor and rounding rule that produces LBSTRESN must also produce LBSTNRLO and LBSTNRHI.


QC checks that matter

Standard conformance validation covers the basics — variable presence, type, codelist membership. It does not cover cross-variable consistency, conversion accuracy, or the structural requirement for the LC domain. These checks need to run as part of your standard SDTM QC suite, not as a one-time review.

The first check is LBSTRESU consistency per LBTESTCD across the full dataset. A single discrepant record passes P21 and fails the analysis. The second is LBSTRESC and LBSTRESN agreement — when LBSTRESN is populated, the numeric portion of LBSTRESC must match it within rounding tolerance. The third is qualitative results with LBSTRESN populated. The fourth is inequality operator preservation — comparing the operator present in LBORRES against what appears in LBSTRESC. The fifth is reference range unit consistency — checking that LBSTNRLO and LBSTNRHI are numerically plausible relative to LBSTRESN and LBSTRESU. The sixth is a factor-based reasonableness check: for each converted record, multiply LBORRES by the expected factor and compare to LBSTRESN. If the difference exceeds the rounding tolerance, the factor is wrong or was applied incorrectly.

P21 does not check: LBSTRESU consistency within a LBTESTCD. Conversion accuracy. Reference range unit plausibility. Whether LC is present when FDA clinical submission requires it. These are your responsibility. If your QC plan relies entirely on P21, it is incomplete.

Most production LB issues are not single-variable problems. They are mismatches between variables that individually look valid.

This is why datasets that pass P21 still fail review.


Bottom line

LB unit conversion fails in the details. A result converts correctly while the reference range does not. A standard unit is assigned directly from the source rather than from controlled metadata, and three records slip through with the wrong LBSTRESU value. A qualitative result gets a numeric encoding for analysis convenience that does not belong in SDTM. An operator disappears during parsing and a boundary value becomes a detected result.

None of these failures are dramatic. They do not crash anything. They travel silently into Define.xml, into ADaM derivations, into safety tables, and into submission review — where a reviewer with a summary statistics macro finds them in a way that is much harder to explain than if they had been caught in QC.

The model is simple enough to keep on one line: LBORRES is collected truth, LBSTRES* is the standardized representation, and LBSTNR* follows the same unit system. Every mistake on this list is a version of breaking that contract. Keep the contract. Run the checks. Make the LB/LC structure decision before lock, not at lock.


Sources

[1] CDISC SDTMIG v3.4 — LB domain variable definitions: LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTNRLO, LBSTNRHI, LBNRIND. LBNRIND documentation requirement in Define.xml. cdisc.org/standards/foundational/sdtmig

[2] FDA Study Data Technical Conformance Guide v6.1, December 2025 — LB/LC two-domain requirement for clinical submissions, traceability expectations. fda.gov/media/153632/download

[3] CDISC Knowledge Base — Standardized Lab Units: FDA expects SI units in LB domain; conventional units in custom LC domain. cdisc.org/kb/articles/standardized-lab-units

[4] PharmaSUG China 2025 (DS175) — "Bridging FDA and SI Unit Requirements through LB and LC." Verbatim SDTCG 6.0 mandate and LC domain structure. lexjansen.com

[5] PMDA Q&A, provisional English translation, March 2025 — SI unit requirement, documentation of conversion equation when original and converted values coexist.

[6] JCTLM (Joint Committee for Traceability in Laboratory Medicine) — Recommended conversion factors for clinical chemistry analytes. jctlm.org

[7] CDISC CT, LB Units codelist C71620 — NCI EVS. evs.nci.nih.gov

Tags: SDTM LB Domain LC Domain LBORRES LBSTRESC LBSTRESN LBSTRESU LBSTNRLO LBNRIND FDA PMDA Pinnacle 21 Define.xml SAS