Skip to content

Rename column when a symbol is named in xnew_data() - #102

Merged
joethorley merged 22 commits into
mainfrom
fix-99-named-symbol-column
Sep 8, 2026
Merged

joethorley merged 22 commits into
mainfrom
fix-99-named-symbol-column

Conversation

@joethorley

@joethorley joethorley commented Aug 31, 2026

Copy link
Copy Markdown
Member

Closes #99. Closes #109.

xnew_data(Year, Length = lengths) produced a data frame column Length$lengths instead of a new column Length, and other named arguments either ignored .length_out or were packed the same way.

A bare symbol is translated into xnew_seq(), which returns a one column tibble named after the symbol.
When the argument was also named, tidyr::expand() stored that tibble as a data frame column.
A named bare vector reached expand() directly, which expands a factor to all of its levels and so discarded .length_out and .obs_only.

Every named argument is now passed through xnew_column(), which applies the argument name to the value itself and drops the outer name:

  • a bare vector is wrapped in a one column tibble of that name;
  • a one column data frame has its column renamed;
  • a multi column data frame, such as tidyr::nesting(), is packed into a data frame column as before;
  • NULL is dropped, as expand() did before.
xnew_data(data.frame(lengths = 1:2), Length = lengths)
#> # A tibble: 2 x 2
#>   lengths Length
#>     <int>  <int>
#> 1       1      1
#> 2       1      2

d <- tibble::tibble(a = 1:5 + 0.5, b = factor(letters[1:5]))
xnew_data(d, z = new_seq(b, .length_out = 2))$z
#> [1] a b
#> Levels: a b c d e

The user's quosure is nested inside one evaluated in the package namespace so the internal helper is in scope without inlining the function object.
The semantics of named arguments are documented in the details section.

Adds regression tests and examples. R CMD check: 0 errors, 0 warnings, 0 notes.

Name handling inside xobs_only() is tracked separately in #111.

🤖 Generated with Claude Code

A named symbol was translated into `xnew_seq()`, which returns a one
column tibble named after the symbol, so `tidyr::expand()` stored it as
a data frame column (`Length$lengths`). The tibble is now renamed and
the argument name dropped so that a new column of that name is
generated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Fixes xnew_data() behavior when a bare-symbol argument is also named (e.g., Length = lengths), which previously caused tidyr::expand() to create a nested data-frame column (printed as Length$lengths) instead of a proper new column.

Changes:

  • Propagate argument names into translation so that named bare symbols are converted to a one-column tibble renamed to the supplied name, while dropping the outer argument name to avoid nested columns in tidyr::expand().
  • Add regression tests covering column naming and .length_out behavior for named symbols (including factors).
  • Add an example documenting that naming a variable creates a new column of that name.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.

File Description
R/xnew-data.R Renames one-column tibbles produced from bare symbols when the argument is named, and drops the outer name to avoid nested columns in tidyr::expand().
tests/testthat/test-xnew-data.R Adds regression tests for named-symbol column creation and .length_out behavior (including factor level preservation).
man/xnew_data.Rd Updates generated documentation examples to illustrate the named-symbol behavior.
Files not reviewed (1)
  • man/xnew_data.Rd: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@StefanoMezzini StefanoMezzini left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

added some tests for using a preexisting vector object instead of a data.frame column

Comment thread tests/testthat/test-xnew-data.R Outdated
Comment thread R/xnew-data.R Outdated
@StefanoMezzini
StefanoMezzini self-requested a review August 31, 2026 17:34

@StefanoMezzini StefanoMezzini left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

accidentally approved instead of requesting changes. see requested changes above

Co-authored-by: Stefano Mezzini <stefano@poissonconsulting.ca>
Co-authored-by: joethorley <613671+joethorley@users.noreply.github.com>

@claude claude 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.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

joethorley and others added 3 commits September 2, 2026 06:20
The comment was dropped from R/xnew-data.R but not from man/xnew_data.Rd,
leaving the roxygen source and the generated Rd out of sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread R/xnew-data.R
Co-authored-by: Stefano Mezzini <stefano@poissonconsulting.ca>
@StefanoMezzini

Copy link
Copy Markdown
Member

The code seems to be working well, but I can't comment on whether the changes are ideal. I understand what the diffused expressions are doing, but I am not familiar enough with the functions to provide any additional useful feedback, so I'll leave that to @krlmlr.

@krlmlr krlmlr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not just

expr_translate_xnew_data <- function(expr, name, length_out) {
  if (!is_symbol(expr)) {
    return(expr)
  }

  if (name == "") {
    # Unnamed: return tibble
    expr(xnew_seq(!!expr, .length_out = !!length_out))
  } else {
    # Named: return first column of tibble as a vector,
    # new name will be applied externally
    expr(xnew_seq(!!expr, .length_out = !!length_out)[[1]])
  }
}

and with just the imap() change? What am I missing?

Comment thread R/xnew-data.R Outdated
Comment thread R/xnew-data.R Outdated
Comment thread R/xnew-data.R Outdated
joethorley and others added 3 commits September 2, 2026 09:15
Co-authored-by: Kirill Müller <krlmlr@users.noreply.github.com>
Co-authored-by: Kirill Müller <krlmlr@users.noreply.github.com>
@krlmlr

This comment was marked as resolved.

@krlmlr

This comment was marked as resolved.

@krlmlr

This comment was marked as resolved.

@krlmlr

krlmlr commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

On the other hand, enquos() might always set names:

names_enquos <- function(...) {
  names(rlang::enquos(...))
}

names_enquos(a = 3)
#> [1] "a"
names_enquos(a, 3)
#> [1] "" ""

Created on 2026-09-02 with reprex v2.1.1

@joethorley

Copy link
Copy Markdown
Member Author

Why not just ... and with just the imap() change? What am I missing?

The one column tibble is load-bearing. expand() sorts and deduplicates either way, so that
is not the distinction, but a bare factor vector is expanded to all of its levels whereas
a factor held in a data frame keeps only the values present:

d <- tibble::tibble(a = 1:5 + 0.5, b = factor(letters[1:5]))

tidyr::expand(d, z = factor(c("b", "a"), levels = letters[1:5]))$z
#> [1] a b c d e
#> Levels: a b c d e
tidyr::expand(d, tibble::tibble(z = factor(c("b", "a"), levels = letters[1:5])))$z
#> [1] a b
#> Levels: a b c d e

That level expansion silently discards .length_out and .obs_only, so with [[1]] the
named symbol respects .length_out (#99) test fails:

`actual`:   "a" "b" "c" "d" "e"
`expected`: "a" "b"

On imap(): you are right that enquos() always sets names, so it is safe here.
names(enquos()) is character(0) and entries are "" otherwise, never NULL.
Worth noting that names(translated)[symbol] <- "" two lines down makes the same
assumption (with NULL names it yields NA names), so hardening one without the other
buys nothing.

Your question did turn up something real though.
The named non-symbol paths never got the tibble wrapper and are still broken:

xnew_data(d, z = new_seq(b, .length_out = 2))$z
#> [1] a b c d e

That is the idiom shown in xnew_seq()'s own examples, so it is reachable straight from the
documentation. Filed as #109 and fixed in #110, which also makes the names2() question moot
by dropping the outer name for every named argument rather than only for symbols.

@joethorley

Copy link
Copy Markdown
Member Author

@krlmlr see response drafted by Fable above.

@krlmlr

krlmlr commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

I was thinking that we could just always wrap in tibbles and pass them as unnamed objects to expand(), thus concentrating the naming logic into one place. This seemed overkill but the named non-symbol case seems convincing. I guess I'm confused by the split in the naming logic across two functions.

@joethorley

Copy link
Copy Markdown
Member Author

This was the problem reprex

library(newdata)
Year <- data.frame(lengths = 1:2)
xnew_data(Year, Length = lengths)
#> # A tibble: 2 × 2
#>   lengths Length$lengths
#>     <int>          <int>
#> 1       1              1
#> 2       1              2

Created on 2026-09-08 with reprex v2.1.1

joethorley and others added 2 commits September 8, 2026 08:32
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A named argument evaluating to NULL produced a zero column tibble that
tidyr::expand() crossed as zero rows; it is now dropped as on main.
The user's quosure is nested inside one evaluated in the package
namespace instead of inlining the function object, so backtraces show
xnew_column() and the quosure stays small.
The name handling reduces to unname() and the details section now
states how named arguments of each shape are treated.
Regenerates the stale Rd.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread R/xnew-data.R
Comment thread R/xnew-data.R
Comment thread R/xnew-data.R
Comment thread R/xnew-data.R
joethorley and others added 4 commits September 8, 2026 11:01
Co-authored-by: Kirill Müller <krlmlr@users.noreply.github.com>
Co-authored-by: Kirill Müller <krlmlr@users.noreply.github.com>
Co-authored-by: Kirill Müller <krlmlr@users.noreply.github.com>
Comment thread R/xnew-data.R Outdated
joethorley and others added 3 commits September 8, 2026 11:44
Passing a data frame where a vector is expected, such as
xnew_data(data, Length = data.frame(lengths = 10:12)), failed with the
S3 dispatch error "no applicable method for 'new_seq'". A default
method now names the supported classes and what was received.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@joethorley
joethorley merged commit 2d18ccc into main Sep 8, 2026
11 checks passed
@joethorley
joethorley deleted the fix-99-named-symbol-column branch September 8, 2026 19:09
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.

Named non-symbol arguments to xnew_data() ignore .length_out or create a packed column Not possible to add new column

5 participants