-
Notifications
You must be signed in to change notification settings - Fork 2.1k
replace parse() with parse_safe() in geom_text() #2867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The built in `parse()` function silently drops items: length(parse(text = c("alpha", "", "gamma"))) We expect the length to be 3, but instead it is 2. So, we add a new function `parse_safe()` that keeps all items. This addresses issue tidyverse#2864
DetailsHere is the code I used to generate the images. dev.off()
library(ggplot2)
library(patchwork)
d1 <- data.frame(
x = c(1, 2, 3),
y = c(1, 0, 1),
label = c("alpha", " ", "gamma"),
stringsAsFactors = FALSE
)
p <- ggplot(d1, aes(x = x, y = y, label = label)) + geom_point(size = 5, color = "red")
p1 <- p + geom_text() + labs(title = "stringsAsFactors = FALSE\nparse = FALSE")
p2 <- p + geom_text(parse = TRUE) + labs(title = "stringsAsFactors = FALSE\nparse = TRUE")
p1 + p2
ggsave(filename = "pullXXX/d1-text.png", plot = p1 + p2, width = 6, height = 3)
p1 <- p + geom_label() + labs(title = "stringsAsFactors = FALSE\nparse = FALSE")
p2 <- p + geom_label(parse = TRUE) + labs(title = "stringsAsFactors = FALSE\nparse = TRUE")
p1 + p2
ggsave(filename = "pullXXX/d1-label.png", plot = p1 + p2, width = 6, height = 3)
d2 <- data.frame(
x = c(1, 2, 3),
y = c(1, 0, 1),
label = c("alpha", " ", "gamma"),
stringsAsFactors = TRUE
)
p <- ggplot(d2, aes(x = x, y = y, label = label)) + geom_point(size = 5, color = "red")
p1 <- p + geom_text() + labs(title = "stringsAsFactors = TRUE\nparse = FALSE")
p2 <- p + geom_text(parse = TRUE) + labs(title = "stringsAsFactors = TRUE\nparse = TRUE")
p1 + p2
ggsave(filename = "pullXXX/d2-text.png", plot = p1 + p2, width = 6, height = 3)
p1 <- p + geom_label() + labs(title = "stringsAsFactors = TRUE\nparse = FALSE")
p2 <- p + geom_label(parse = TRUE) + labs(title = "stringsAsFactors = TRUE\nparse = TRUE")
p1 + p2
ggsave(filename = "pullXXX/d2-label.png", plot = p1 + p2, width = 6, height = 3) |
Several comments:
|
Does this version of Hadley's original function do what you need? [Though I still don't understand why a list of expressions won't work. It does in my testing with parse_hadley <- function(text) {
text <- as.character(text)
out <- vector("list", length(text))
for (i in seq_along(text)) {
expr <- parse(text = text[[i]])
out[[i]] <- if (length(expr) == 0) "" else expr[[1]]
}
do.call(expression, out)
} |
I think |
Claus: This pull request was created before our nice discussion in #2864. It needs to be updated. Regarding each of your comments:
Your code appears to work when you test it with |
I've figured out why lists of expressions don't work here but seem to work in other parts of ggplot2. We're actually converting them back to expression objects, e.g. here: Lines 35 to 41 in 71cb174
It's good to keep that in mind, as I think the ggplot2 code is a bit inconsistent in this regard. For example, my original code was inspired by this line: Line 481 in 34c01cc
Also, functions in the scales package return lists of expressions rather than expression objects: > scales::math_format()(1:5)
[[1]]
10^`1`
[[2]]
10^`2`
[[3]]
10^`3`
[[4]]
10^`4`
[[5]]
10^`5` In summary: I agree with your latest approach to |
Use Hadley's code to parse the expressions once instead of twice. Use `parse(..., keep.source = FALSE)`, as Claus recommended. Add an example to demonstrate why `parse_safe()` is needed.
I agree that there might be other places, in addition to It would be nice to demonstrate that they suffer from the same problem before I modify them. Please share if you have any examples that demonstrate the issue! |
Kamil, here you go. I assume it's caused by this line: Line 481 in 34c01cc
Reprex: library(ggplot2)
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
# works
ggplot(nc) +
geom_sf(aes(fill = AREA)) +
scale_y_continuous(
breaks = c(34, 35, 36),
labels = c("34*degree*N", "35*degree*N", "36*degree*N")
) # breaks
ggplot(nc) +
geom_sf(aes(fill = AREA)) +
scale_y_continuous(
breaks = c(34, 35, 36),
labels = c("34*degree*N", "", "36*degree*N")
)
#> Error in parse(text = x)[[1]]: subscript out of bounds Created on 2018-08-30 by the reprex package (v0.2.0). |
This patch fixes an example that triggers an error: library(ggplot2) nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) ggplot(nc) + geom_sf(aes(fill = AREA)) + scale_y_continuous( breaks = c(34, 35, 36), labels = c("34*degree*N", "", "36*degree*N") ) #> Error in parse(text = x)[[1]]: subscript out of bounds See tidyverse#2867 for more details.
Thanks, Claus. My commit a52e7fd fixes the |
Here is another example of this issue (the alpha should be on the first facet): library(ggplot2)
mtcars$cyl2 <- factor(mtcars$cyl, labels = c("alpha", "beta", "gamma"))
mtcars$cyl2 <- as.character(mtcars$cyl2)
mtcars$cyl2[mtcars$cyl2 == "beta"] <- ""
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
p + facet_grid(. ~ cyl2, labeller = label_parsed) Here's the relevant code: Lines 152 to 169 in 510b4b4
It turns out I'm wrong. The example works just fine! The "beta" data points are displayed before the "alpha" data points, so it is ok. To see that everything is ok, compare this figure to the previous one: p + facet_grid(. ~ cyl, labeller = label_parsed) |
This patch fixes an example that triggers an error: library(ggplot2) nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) ggplot(nc) + geom_sf(aes(fill = AREA)) + scale_y_continuous( breaks = c(34, 35, 36), labels = c("34*degree*N", "", "36*degree*N") ) #> Error in parse(text = x)[[1]]: subscript out of bounds See tidyverse#2867 for more details.
As far as I can tell, there is only one more location where we might be interested to use Lines 317 to 320 in 510b4b4
I would leave this as is, because:
What do you think? Is there anything missing in this pull request? |
No need to touch deprecated functions. But I do think it's worth using the same code in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments on the organisation
R/utilities.r
Outdated
@@ -430,3 +430,24 @@ is_column_vec <- function(x) { | |||
dims <- dim(x) | |||
length(dims) == 2L && dims[[2]] == 1L | |||
} | |||
|
|||
#' Parse a vector of expressions without silently dropping any items. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is not exported, I don't think it needs to be documented. A comment mentioning that parse(text = text)
has subtle problems with link to issue should be adequate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a feeling future developers will appreciate the hints in the documentation. I know I was definitely surprised by the default behavior of parse()
dropping things. Totally unexpected for me.
Regarding the link, is #2864
sufficient or do you prefer to put a full url like https://github.com/tidyverse/ggplot2/issues/2864
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Hadley meant you can write #
instead of #'
at the beginning of the line. Writing issue #2864
is sufficient to refer to the issue. That's how we do it in the NEWS.md
file also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, I'd actually prefer the full url since that is clickable in RStudio. I'd suggest something like:
# Parse takes n lines and returns m _expressions_
# See https://github.com/tidyverse/ggplot2/issues/2864 for discussion
tests/testthat/test-parse.r
Outdated
@@ -0,0 +1,52 @@ | |||
context("Parsing expressions") | |||
|
|||
expect_equal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need to live a couple of test_that()
blocks in test-utilities.R
- see detail in http://style.tidyverse.org/tests.html#organisation (ggplot2 doesn't fully follow this organisation but should move towards it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it! I'll move the tests into test-utilities.R
tests/testthat/test-parse.r
Outdated
expression(alpha, NA, gamma, NA) | ||
) | ||
|
||
expect_equal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should put all "multi" expressions into one test
tests/testthat/test-parse.r
Outdated
expression(alpha, NA, integral(f(x) * dx, a, b)) | ||
) | ||
|
||
expect_equal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't test anything new?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, some of the tests are probably redundant. I'll try to reduce redundancy.
tests/testthat/test-parse.r
Outdated
expression(NA, 1, NA, 2) | ||
) | ||
|
||
expect_equal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this tests anything new either.
I don't understand the code in |
R/sf.R
Outdated
@@ -553,11 +549,18 @@ CoordSf <- ggproto("CoordSf", CoordCartesian, | |||
graticule <- panel_params$graticule | |||
east <- graticule[graticule$type == "E" & !is.na(graticule$degree_label), ] | |||
|
|||
labs <- east$degree_label | |||
|
|||
# parse labels into expressions if required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're actually fixing another problem here at the same time, which is good. (The problem is that we may have to parse only labels along one axis.) However, this distributes the autoparsing to two separate locations. Maybe you could document this a little better by stating that labels created by sf::st_graticule()
contain degree symbols and need to be parsed to look right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kamil, I've been pondering this some more. It's bugging me that we're breaking the parsing into two different places because I have a PR pending where we're labeling based on cardinal directions (N, S, E, W) rather than plot side and it can shuffle which labels appear where, and that will break with your current code. Is there any reason why we can't just do something like the following (using parse_safe()
instead of parse()
, though):
graticule <- data.frame(
degree_label = c("abcd", "b", "10 * degree * E"),
stringsAsFactors = FALSE
)
parse_ids <- grepl("degree", graticule$degree_label)
if (any(parse_ids)) {
graticule$degree_label <- ifelse(
parse_ids,
parse(text = graticule$degree_label),
as.expression(graticule$degree_label)
)
}
graticule$degree_label
#> expression("abcd", "b", 10 * degree * E)
This will keep strings as strings but stick everything into an expression object so we don't have to worry about which label is of which type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I broke the parsing into two different places to avoid having an expression()
column in the graticule
dataframe.
Here is an example that shows the problem I ran into:
graticule <- data.frame(
type = c("E", "E", "E"),
degree_label = c(NA, "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
# This works if degree_label is not an 'expression'
east <- graticule[graticule$type == "E" & !is.na(graticule$degree_label), ]
parse_ids <- grepl("degree", graticule$degree_label)
if (any(parse_ids)) {
graticule$degree_label <- ifelse(
parse_ids,
parse(text = graticule$degree_label),
as.expression(graticule$degree_label)
)
}
graticule
#> Warning in format.data.frame(x, digits = digits, na.encode = FALSE):
#> corrupt data frame: columns will be truncated or padded with NAs
#> type degree_label
#> 1 E expression(NA_character_, 10 * degree * E, 15 * degree * E)
#> 2 E <NA>
#> 3 E <NA>
graticule$degree_label
#> expression(NA_character_, 10 * degree * E, 15 * degree * E)
is.na(graticule$degree_label)
#> Warning in is.na(graticule$degree_label): is.na() applied to non-(list or
#> vector) of type 'expression'
#> [1] FALSE FALSE FALSE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, I should know better than to paste any code without proper reprex. I had tried the code except the final assignment to a data frame, which I assumed would just work.
The following should actually work. (It's basically what the code does currently, but with being a bit smarter about which labels to parse.)
graticule <- data.frame(
type = c("E", "E", "E", "E"),
degree_label = c(NA, "abcd", "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
parse_ids <- grepl("degree", graticule$degree_label)
if (any(parse_ids)) {
graticule$degree_label <- ifelse(
parse_ids,
lapply(graticule$degree_label, function(x) parse(text = x)[[1]]),
lapply(graticule$degree_label, function(x) as.expression(x)[[1]])
)
}
graticule$degree_label
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] "abcd"
#>
#> [[3]]
#> 10 * degree * E
#>
#> [[4]]
#> 15 * degree * E
Created on 2018-09-01 by the reprex package (v0.2.0).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And to show that this creates a proper data frame:
graticule <- data.frame(
type = c("E", "E", "E", "E"),
degree_label = c(NA, "abcd", "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
parse_ids <- grepl("degree", graticule$degree_label)
if (any(parse_ids)) {
graticule$degree_label <- ifelse(
parse_ids,
lapply(graticule$degree_label, function(x) parse(text = x)[[1]]),
lapply(graticule$degree_label, function(x) as.expression(x)[[1]])
)
}
graticule
#> type degree_label
#> 1 E NA
#> 2 E abcd
#> 3 E 10 * degree * E
#> 4 E 15 * degree * E
Created on 2018-09-01 by the reprex package (v0.2.0).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'm starting to understand the motivation for wrapping expressions in lists here and also in label_parsed()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting the conditional inside the mapping than the other way round is probably more elegant.
graticule <- data.frame(
type = c("E", "E", "E", "E"),
degree_label = c(NA, "abcd", "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
parse_ids <- grepl("degree", graticule$degree_label)
if (any(parse_ids)) {
graticule$degree_label <- Map(
function(b, l) {
if (b) {
parse(text = l)[[1]]
} else {
as.expression(l)[[1]]
}
},
parse_ids, graticule$degree_label
)
}
graticule
#> type degree_label
#> 1 E NA
#> 2 E abcd
#> 3 E 10 * degree * E
#> 4 E 15 * degree * E
Created on 2018-09-02 by the reprex package (v0.2.0).
Thanks for your patience on this pull request! I hope we're getting closer to the end. What are your comments?
Regarding
|
R/sf.R
Outdated
parse_ids, graticule$degree_label | ||
) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind moving this code block into the fixup_graticule_labels()
function?
This all looks good to me. I have one last, minor comment. Could you please move the label parsing code into the function |
R/utilities.r
Outdated
@@ -430,3 +430,21 @@ is_column_vec <- function(x) { | |||
dims <- dim(x) | |||
length(dims) == 2L && dims[[2]] == 1L | |||
} | |||
|
|||
# Parse takes takes n strings and returns n expressions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed "takes takes". Since I asked for one more substantive change, maybe you could fix this also.
Hadley, do you still prefer that I think this might be the last issue to agree upon in this pull request. |
R/utilities.r
Outdated
@@ -430,3 +430,21 @@ is_column_vec <- function(x) { | |||
dims <- dim(x) | |||
length(dims) == 2L && dims[[2]] == 1L | |||
} | |||
|
|||
# Parse takes n strings and returns n expressions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be: "Parse takes a vector of n lines and returns m expressions."
I would like to understand what |
NEWS.md
Outdated
@@ -59,6 +59,11 @@ | |||
* `position_nudge()` is now more robust and nudges only in the direction | |||
requested. This enables, for example, the horizontal nudging of boxplots | |||
(@clauswilke, #2733). | |||
|
|||
* `geom_text(..., parse = TRUE)` now correctly renders the expected number of | |||
items instead of silently dropping items that are not valid expressions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not valid isn't quite the right turn of phrase - they are valid; they're just empty. We also fixed handling of multiple expressions in a single string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, will try to make the comment more accurate.
R/sf.R
Outdated
if (parse_id) { | ||
parse(text = label)[[1]] | ||
} else { | ||
as.expression(label)[[1]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we turning label
into an expression object and then immediately extracting it out of the expression object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we need the symbol itself, not expression(symbol)
. This is directly copied from the current code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem with this code chunk as a whole, is that when I read it I can't easily tell what type of object graticule$degree_label
is supposed to be. It's obviously a list, and some of the components are expressions (not expression objects), but what are the others supposed to be?
You could fix that with a comment, but it would be even better to use functions where it's obvious what the output type is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need a symbol, and we have a character vector of length one, then the function to use is as.symbol()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was confused. This can definitely be simplified.
as.expression(label)[[1]]
came from an earlier version of the code where I stuck everything into an expression object and thus needed as.expression(label)
. But now everything goes into a list, and strings can go directly since they are self-quoting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so my gut feeling was right and the code I suggest below is a bit clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, correct. See also my other comment, below.
R/sf.R
Outdated
# Convert the string 'degree' to the degree symbol | ||
parse_ids <- grepl("\\bdegree\\b", graticule$degree_label) | ||
if (any(parse_ids)) { | ||
graticule$degree_label <- Map( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not use Map()
here - I suspect you could more simply right as something like:
labels <- as.list(graticule$degree_label)
labels[parse_ids] <- lapply(labels[parse_ids], parse_safe)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is much simpler. For some reason I always forget that we can assign to a subset of elements in a list. However, it still needs the part where only the first element of the expression object is extracted, so we get the element itself not inside an expression()
statement:
labels <- as.list(graticule$degree_label)
labels[parse_ids] <- lapply(labels[parse_ids], function(x) parse_safe(x)[[1]])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh but parse_safe is already vectorised so you can skip the lapply
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you apply the character vector that hasn’t been coerced to a list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. It may be best to work with a full reprex. The following works:
parse_safe <- function(text) {
out <- vector("expression", length(text))
for (i in seq_along(text)) {
expr <- parse(text = text[[i]])
out[[i]] <- if (length(expr) == 0) NA else expr[[1]]
}
out
}
graticule <- data.frame(
type = c("E", "E", "E", "E"),
degree_label = c(NA, "abcd", "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
needs_parsing <- grepl("degree", graticule$degree_label)
labels <- as.list(graticule$degree_label)
labels[needs_parsing] <- lapply(labels[needs_parsing], function(x) parse_safe(x)[[1]])
graticule$degree_label <- labels
graticule
#> type degree_label
#> 1 E NA
#> 2 E abcd
#> 3 E 10 * degree * E
#> 4 E 15 * degree * E
Do you see a way to make it simpler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're correct, it can be made simpler.
parse_safe <- function(text) {
out <- vector("expression", length(text))
for (i in seq_along(text)) {
expr <- parse(text = text[[i]])
out[[i]] <- if (length(expr) == 0) NA else expr[[1]]
}
out
}
graticule <- data.frame(
type = c("E", "E", "E", "E"),
degree_label = c(NA, "abcd", "10 * degree * E", "15 * degree * E"),
stringsAsFactors = FALSE
)
needs_parsing <- grepl("degree", graticule$degree_label)
labels <- as.list(graticule$degree_label)
labels[needs_parsing] <- parse_safe(graticule$degree_label[needs_parsing])
graticule$degree_label <- labels
graticule
#> type degree_label
#> 1 E NA
#> 2 E abcd
#> 3 E 10 * degree * E
#> 4 E 15 * degree * E
Created on 2018-09-04 by the reprex package (v0.2.0).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that’s exactly what I was imagining.
R/sf.R
Outdated
if (any(grepl("degree", graticule$degree_label))) | ||
graticule$degree_label <- lapply(graticule$degree_label, function(x) parse(text = x)[[1]]) | ||
# Convert the string 'degree' to the degree symbol | ||
parse_ids <- grepl("\\bdegree\\b", graticule$degree_label) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be more informative if called needs_parsing
or similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree!
`parse_safe()` takes a character vector, not a factor
Hadley, I think the labeller functions have some issues that go beyond the scope of this pull request... it's difficult to write up my findings because I am running into unexpected behavior as I try to make examples. I agree with you that we ought to discuss this issue separately. |
Thanks Kamil! |
Claus and Hadley, thanks for helping me with this pull request! 🐄 Claus, I am curious to know how you made such a nice merge. I'm still learning how to use GitHub's collaboration features, and pull requests make me think twice before I do anything. I'm impressed that you've squashed everything into one big commit with a nice summary of all the commit messages. Also, I just noticed that there is a checkbox on the right that says |
Squash-merge is the default on github. I just need to click a button. :-) GitHub then opens a window where you can edit the commit message, and that's where I inserted "Closes issue #2864". |
This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/ |
The built in
parse()
function silently drops items:We expect the length to be 3, but instead it is 2.
So, we add a new function
parse_safe()
that keeps all items.This addresses issue #2864