Why Reliable Importing Matters
A model output is not just a collection of numbers. Each value has a name, a role, a unit, and often a software-specific meaning. If an identifier is converted from a character string to a number, a parameter name is silently changed, or a sentinel is mistaken for a measurement, the resulting R object may look usable while representing something different from the original output.
Reliable importing therefore means more than successfully reading a file. It means making the import contract explicit: which file and delimiter are used, what each column means, how identifiers and units are represented, and which values count as missing. NONMEM and Monolix commonly write delimited output files whose precise names and layouts depend on the run configuration and software version. An nlmixr2 fit, by contrast, is an R object; its components and available methods should be inspected rather than treated as a generic text table.
Start With the Import Contract
Before reading a file, record its source, delimiter, header status, expected columns, units, and documented missing-value conventions. A useful contract also states which columns identify a row. For a prediction table, that might be subject ID and time; for a parameter table, it might be parameter name. A table that has been read without errors can still violate this contract.
Names deserve particular care. R may alter names to make them syntactically convenient unless instructed otherwise. For example, a source name such as THETA(1) may be changed during import. Keep the original name when it is needed for mapping or auditability; use explicit column indexing such as df[["THETA(1)"]] rather than assuming every name can be used directly as an R symbol.
Identifiers should generally be read as character data unless there is a clear reason to treat them numerically. The strings "001" and "1" may identify different records even though they have the same numeric value. Likewise, an ID is usually a label, not a quantity for which a mean is meaningful. A row key should also be checked for unexpected duplicates: if subject and time are intended to identify a row, repeated subject-time pairs may indicate either legitimate replicates or an import or data-preparation problem that needs explanation.
Importing NONMEM and Monolix Tables
NONMEM and Monolix can produce different kinds of output files, and their layouts can vary with settings and software version. A table may be separated by tabs or whitespace, may have a header or not, and may contain software-specific missing-value codes. Confirm these properties from the run configuration or the file itself before choosing import arguments. Do not assume every file from a given program shares one universal format.
For a whitespace-delimited NONMEM table with a header, base R's read.table allows the delimiter and name handling to be specified directly. Explicit column classes can protect an ID column from numeric conversion. For example, if a documented file contains an ID, time, observation, prediction, and a configured missing observation code, a starting import could be:
nm <- read.table("sdtab.csv", header = TRUE, sep = "", check.names = FALSE, colClasses = c("character", "numeric", "numeric", "numeric", "numeric"))
The filename in this example is illustrative. Use the actual file and its actual column layout; the colClasses entries must match the columns in order. If the file is comma- or tab-delimited, specify that delimiter instead of using whitespace separation. When the file has no header, do not set header = TRUE: provide verified column names separately and check that the number of fields matches.
Missing-value codes must be treated as data conventions, not guessed from appearance. A value such as -9999 could be a missing marker in a particular output, but it is not safe to convert every occurrence of that number to NA unless the file specification says it is a sentinel for that field. It could instead be a legitimate value or have a different meaning in another column. Apply recoding only to the affected columns and record the rule.
Worked Example: Importing a NONMEM Prediction Table
Suppose a hypothetical whitespace-delimited NONMEM table has a header and these rows. The run documentation states that -9999 in DV marks an unavailable observation; it is not a valid observation value.
ID TIME DV PRED001 0 -9999 0.00001 1 2.40 2.35002 0 1.80 1.75
Import the ID as character and the remaining columns as numeric, preserving the source column names. Because the file uses whitespace separation, sep = "" is appropriate:
nm <- read.table("hypothetical_nonmem_table.txt", header = TRUE, sep = "", check.names = FALSE, colClasses = c("character", "numeric", "numeric", "numeric"))
Now apply the documented sentinel rule to DV, and only to DV:
nm$DV[nm$DV == -9999] <- NA_real_
The imported ID values remain "001" and "002", rather than becoming 1 and 2. There are three rows and one unavailable observation, so the fraction of missing DV values is \(1/3 = 0.3333\). The PRED value for the first row remains 0.00; replacing the sentinel in DV does not erase or alter that prediction.
For Monolix, exported result tables are also delimited files, but available files, headers, and missing-value representations depend on the export and version. Inspect the exact file and its associated settings. Do not infer units, column meaning, or sentinel codes from a filename alone. If IDs could have leading zeros, protect them as character data in the same way as for a NONMEM table.
Worked Example: Preserving Units in a Monolix Export
Consider a hypothetical Monolix prediction export with columns ID, TIME, and Cpred_ng_mL. The export documentation states that a period in the prediction column means “prediction unavailable,” time is in hours, and concentration is in nanograms per millilitre. The file contains:
ID TIME Cpred_ng_mL010 0 1200010 1 .011 1 850
Read the ID as character and interpret the period as missing only because the export documentation defines it that way. For example:
mlx <- read.table("hypothetical_monolix_export.txt", header = TRUE, sep = "", check.names = FALSE, colClasses = c("character", "numeric", "character"))
Convert the prediction column explicitly. The conversion also makes the missing-value rule visible:
mlx$Cpred_ng_mL[mlx$Cpred_ng_mL == "."] <- NA_character_mlx$Cpred_ng_mL <- as.numeric(mlx$Cpred_ng_mL)
If a downstream analysis requires milligrams per litre, use the unit relation \(1\ \mathrm{ng/mL} = 0.001\ \mathrm{mg/L}\). Thus the first prediction converts as \(1200 \times 0.001 = 1.200\ \mathrm{mg/L}\), and the third as \(850 \times 0.001 = 0.8500\ \mathrm{mg/L}\). The unavailable prediction stays missing. Keep either the original column or a clearly named converted column so that the conversion can be audited; do not relabel the original numbers as milligrams per litre without converting them.
Importing an nlmixr2 Result Object
An nlmixr2 fit is not a delimited file. It is an R object that can contain estimates and other fit results in structured components. Inspect the object class and structure, and consult the documentation for the installed nlmixr2 version to identify the supported component or accessor for the quantity you need. Do not scrape printed console output: printed summaries are for display and may change formatting, rounding, or labels.
For fixed-effect estimates, a named estimate vector can be turned into a table without discarding the original parameter names. In versions whose fit object exposes fixed estimates as fit$parFixed, the basic extraction pattern is:
theta <- fit$parFixedstopifnot(!is.null(names(theta)))theta_table <- data.frame(parameter = names(theta), estimate = unname(theta), row.names = NULL)
Check that this component is present and has the expected meaning for the fitted object before using the pattern. A fitted object may also provide a formatted parameter table, individual estimates, covariance information, or other results; those are not interchangeable. For example, population fixed-effect estimates are not individual random-effect estimates. If the required values are not in the component you inspected, use the documented method for that result instead of treating another component as equivalent.
Worked Example: Extracting Named Estimates From an nlmixr2 Fit
Suppose a hypothetical nlmixr2 fit object has been checked against the installed version's documentation, and its parFixed component contains named fixed-effect estimates: CL = 5.2 and V = 30. For this example, the model specification says that CL is in litres per hour and V is in litres.
First extract the named vector and build a two-column table:
theta <- fit$parFixedtheta_table <- data.frame(parameter = names(theta), estimate = unname(theta), row.names = NULL)
The resulting table has rows CL, 5.2 and V, 30. The estimates retain their names, while the unit information comes from the model specification rather than from the numeric vector itself. A separate metadata table can attach those units:
units <- c(CL = "L/h", V = "L")theta_table$unit <- unname(units[theta_table$parameter])
The lookup assigns L/h to CL and L to V. Before using this table, check that every parameter name has a unit entry and that no unexpected name has appeared. A result object does not by itself guarantee that an estimate has the unit assumed by a later workflow.
Validation: A Short Justification and Practical Checks
The reason for explicit validation can be stated simply. An import maps source records into R values. If the mapping changes an identifier, name, unit, or missing-value meaning, then calculations on the imported object operate on a different representation of the intended data. Therefore, successful parsing is necessary but not sufficient: the import must also preserve the fields that define the interpretation of each record.
This is a conceptual checklist, not a numeric scoring rule. In practice, inspect names, types, and dimensions immediately after import. Compare row counts with the source file or run report; check that IDs and times have plausible values; check missingness by column; and test the intended row key. For a subject-time key, one useful check is that the number of distinct pairs equals the number of rows when duplicates are not expected. If replicates are expected, define the additional key fields rather than deleting duplicate rows automatically.
Units are often absent from a numeric file, so retain them in accompanying metadata or in unambiguous column names. Check units before combining outputs from different tools. Similarly, parameter names should be mapped explicitly when software uses different naming conventions. The earlier material on mapping parameter names and values between tools is useful for defining that mapping; importing should preserve enough information to apply it, not silently perform an undocumented rename.
Alternatives, Diagnostics, and Failure Modes
Base R readers such as read.table and read.csv are useful when the file is simple and the import settings can be specified directly. A package reader can be convenient for repeated workflows and explicit column specifications. Whichever reader is chosen, check its defaults for name repair, missing-value parsing, and type guessing. Automatic type guessing is convenient for exploration but less reliable for a repeatable pipeline, especially when a column contains identifiers or a mixture of numeric values and special codes.
Common warning signs include unexpectedly changed names, IDs with lost leading zeros, numeric columns imported as character, character columns converted to numbers, a sudden change in row count, and implausible values after conversion. A conversion warning is evidence to investigate, not a reason to suppress the warning and continue. Compare the problematic source fields with the import contract and determine whether the cause is a delimiter error, a header mismatch, a documented missing marker, or a genuinely malformed record.
A common mistake is to replace every negative value with missing data because one output column uses a negative sentinel. The reasoning is wrong because missing-value codes are specific to a file and sometimes to a field; a negative estimate or observation may be valid. The correction is to consult the source convention, recode only the specified field and code, and report how many values were changed. Another mistake is to rename or convert columns before recording their original names and units. Preserve the raw import or a clear mapping so that transformations can be reconstructed.
These safeguards cannot establish that a model was correctly fitted or that outputs from separate software are equivalent. They establish that the values being compared or reused in R have been imported and labelled coherently. They also do not replace inspection of software-specific output documentation, which remains essential when file layouts or conventions vary.
Check Your Understanding
Answer each question, then compare your reasoning with the brief solutions.
- A source ID is
007. Why might importing it as numeric be unsafe? - A NONMEM table uses
-9999for missingDVvalues. What should you verify before replacing that code withNA? - A Monolix prediction is 2500 ng/mL. Convert it to mg/L using \(1\ \mathrm{ng/mL} = 0.001\ \mathrm{mg/L}\).
- An nlmixr2 summary prints rounded estimates, but the fit object contains named estimates. Which is preferable for a repeatable extraction, and why?
Solutions. (1) Numeric conversion changes "007" to 7 and can merge identifiers that were distinct as strings. (2) Verify from the relevant output documentation that this code marks missingness in DV, and check that it is not a valid value; do not automatically recode other columns. (3) \(2500 \times 0.001 = 2.500\ \mathrm{mg/L}\). (4) Use the documented named component or accessor after checking its meaning and structure; it preserves machine-readable values and names rather than depending on display formatting and rounding.