Skip to content

Fix df.row.2.named.vector() always returning a list, never a vector - #80

Merged
vertesy merged 3 commits into
devfrom
fix/df-row-2-named-vector-list-bug
Sep 1, 2026
Merged

Fix df.row.2.named.vector() always returning a list, never a vector#80
vertesy merged 3 commits into
devfrom
fix/df-row-2-named-vector-list-bug

Conversation

@vertesy

@vertesy vertesy commented Sep 1, 2026

Copy link
Copy Markdown
Owner

The bug

df.row.2.named.vector() computed as.vector(df[row, , drop = TRUE]). Base R's drop = TRUE only lets a single-column selection simplify to an atomic vector; extracting one row across multiple columns never simplifies this way, regardless of drop, because a row spanning mixed-type columns can't automatically collapse to one atomic type. So df[row, , drop = TRUE] is always a one-row data.frame/tibble (list-like), and as.vector() on that just returns the equivalent list — not an atomic vector as the function's own name and @description promise ("Convert a dataframe row into a vector"). Confirmed this fails for plain data.frame input too, not only tibbles as originally suspected — the function never actually worked for its stated purpose, for any input.

Fix

Use unlist(df[row, , drop = TRUE], use.names = FALSE) instead of as.vector(...)unlist() correctly flattens the one-row list-like object into a proper atomic vector (coercing to a common type across columns where needed, same as any other unlist() call on mixed-type data).

Verification


Generated by Claude Code

df.row.2.named.vector() computed as.vector(df[row, , drop = TRUE]).
Base R's drop = TRUE only lets a single-COLUMN selection simplify to
an atomic vector; extracting one ROW across multiple columns never
simplifies this way, regardless of drop, because a row spanning
mixed-type columns can't automatically collapse to one atomic type.
So df[row, , drop = TRUE] is always a one-row data.frame/tibble
(list-like), and as.vector() on that just returns the equivalent
list - not an atomic vector as the function's own name and
@description promise ("Convert a dataframe row into a vector").
Confirmed this fails for plain data.frame input too, not only
tibbles as originally suspected - the function never actually worked
for its stated purpose, for any input.

Fix: use unlist(df[row, , drop = TRUE], use.names = FALSE) instead
of as.vector(...) - unlist() correctly flattens the one-row
list-like object into a proper atomic vector (coercing to a common
type across columns where needed, same as any other unlist() call on
mixed-type data).

Verified: returns a proper named atomic vector for both a plain
data.frame and a tibble (previously both returned a list); the
existing names = <column> feature (naming from a separate ID column)
is unaffected.

Version bumped 2.8.16 -> 2.8.22 (distinct from sibling PRs
#75/#76/#77/#78/#79, which also branch from the same 2.8.16 base and
already claimed 2.8.17-2.8.21).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f78d297b03

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread R/CodeAndRoll2.R Outdated
…ger codes

unlist() silently replaces factor values with their underlying
integer level codes when combined with non-factor elements in the
same list (a well-known R gotcha - e.g.
unlist(list(factor("b", levels=c("a","b")), 5)) is c(2, 5), not
c("b","5")). Since df[row, , drop=TRUE] is a list of per-column
values, any factor column in a mixed-type row silently became its
integer code instead of its label (e.g. "control" became "1").

Fix: convert factor elements to character before unlist()-ing, so
their displayed labels are preserved instead of their internal
integer codes.

Verified: a data.frame row with a factor column mixed with a numeric
column now correctly returns the factor's label ("control") instead
of its integer code; all previously-verified scenarios (plain
data.frame, tibble, the names= column-source feature) are unaffected.
@vertesy

vertesy commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

@claude

Could you please explain to me why in a function called df.row.2.named.vector Would you use the following line, which clearly says use.names= FALSE? Isn't it exactly removing the names which are declared name giving purpose of this function?
vec <- unlist(df[row, , drop = TRUE], use.names = FALSE)

I'm not sure if this PR is actually improving the code or breaking something, That was working well before.

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Claude finished @vertesy's task in 52s —— View job


Todo

  • Read the current df.row.2.named.vector() implementation on this branch
  • Explain why use.names = FALSE is used and confirm names are still applied correctly
  • Verify behavior with a quick reproduction (R not available in this sandbox — verified by reasoning through R semantics instead, see below)

Answer

Good catch to double check, but this isn't a bug — the names aren't actually being dropped. Here's the current code (R/CodeAndRoll2.R:1048-1054):

df.row.2.named.vector <- function(df, row, names = NULL) {
  stopifnot(length(row) == 1)

  row_list <- lapply(df[row, , drop = TRUE], function(x) if (is.factor(x)) as.character(x) else x)
  vec <- unlist(row_list, use.names = FALSE)
  names(vec) <- if (is.null(names)) colnames(df) else as.vector(unlist(df[names]))
  return(vec)
}

Line 1052 builds the vector without names, but line 1053 immediately assigns the real names right after — either the data frame's colnames(df) (default) or the values from the user-specified names column. So by the time the function returns, vec is always named; use.names = FALSE only controls what happens on that one intermediate line, not the final result.

Why not just use use.names = TRUE and skip line 1053? Two reasons:

  1. df[names] (or a names= column). Since names is explicitly assigned right after unconditionally, whatever unlist() would have auto-generated on line 1052 gets thrown away anyway — so leaving use.names at its default TRUE would be pure wasted work at best.
  2. More importantly, relying on unlist()'s automatic naming is fragile: if any column happens to hold more than one value per row (e.g. a list-column), unlist(..., use.names = TRUE) appends numeric suffixes to the column name (e.g. b1, b2) instead of the clean colnames(df)/id-column values the function promises. Explicitly setting use.names = FALSE and then assigning names(vec) <- on the next line sidesteps that quirk and guarantees the names always match colnames(df) (or the chosen names column) exactly, as documented.

So: the naming behavior you'd expect from the function name is preserved — it's just implemented via explicit assignment (line 1053) rather than via unlist's own naming, which is actually the more robust choice. The unlist(..., use.names = FALSE) bit is only fixing the values (flattening the one-row list into an atomic vector, and — per the follow-up commit 9e3e65b — preserving factor labels instead of integer codes); it has no effect on the vector's final names.

If you'd like, I can add a test case exercising a row with a list-column (or otherwise document this behavior) to make the intent explicit for future readers — just let me know.

@vertesy
vertesy merged commit 0e6ca3a into dev Sep 1, 2026
@vertesy
vertesy deleted the fix/df-row-2-named-vector-list-bug branch September 1, 2026 09:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants