Skip to content

geom_sf : error when using comma as decimal separator #3365

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

Closed
dicorynia opened this issue Jun 17, 2019 · 14 comments · Fixed by #3378
Closed

geom_sf : error when using comma as decimal separator #3365

dicorynia opened this issue Jun 17, 2019 · 14 comments · Fixed by #3378
Labels
bug an unexpected problem or unintended behavior coord 🗺️

Comments

@dicorynia
Copy link

dicorynia commented Jun 17, 2019

Hi.

There is an error when plotting a map of a sf object if the decimal separator is set to the comma in R options.

library(tidyverse)
library(sf)

nc <- st_read(system.file("shape/nc.shp", package = "sf"))

# works fine
ggplot() +
  geom_sf(data = nc)

# raises an error
 options(OutDec = ",")
 ggplot() +
   geom_sf(data = nc)

#>Error in parse(text = text[[i]]) : <text>:1:3: unexpected ','
#>1: 34,
      ^
traceback
traceback()
#>15: parse(text = text[[i]])
#>14: parse_safe(graticule$degree_label[needs_parsing])
#>13: f(..., self = self)
#>12: self$fixup_graticule_labels(graticule, scale_x, scale_y, params)
#>11: f(..., self = self)
#>10: self$coord$setup_panel_params(scale_x, scale_y, params = self$coord_params)
#>9: (function (scale_x, scale_y) 
#>   {
#>       self$coord$setup_panel_params(scale_x, scale_y, params = self$coord_params)
#>   })(dots[[1L]][[1L]], dots[[2L]][[1L]])
#>8: mapply(FUN = f, ..., SIMPLIFY = FALSE)
#>7: Map(setup_panel_params, scales_x, scales_y)
#>6: f(..., self = self)
#>5: layout$setup_panel_params()
#>4: ggplot_build.ggplot(x)
#>3: ggplot_build(x)
#>2: print.ggplot(x)
#>1: (function (x, ...) 
#>   UseMethod("print"))(x)

As a workaround we can revert to the dot decimal separator before the plot...

session info ``` sessionInfo() #> R version 3.6.0 (2019-04-26) #> Platform: x86_64-w64-mingw32/x64 (64-bit) #> Running under: Windows 7 x64 (build 7601) Service Pack 1 #> #> Matrix products: default #> #> locale: #> [1] LC_COLLATE=French_France.1252 LC_CTYPE=French_France.1252 #> [3] LC_MONETARY=French_France.1252 LC_NUMERIC=C #> [5] LC_TIME=French_France.1252 #> #> attached base packages: #> [1] stats graphics grDevices utils datasets methods base #> #> other attached packages: #> [1] sf_0.7-4 forcats_0.4.0 stringr_1.4.0 dplyr_0.8.1 #> [5] purrr_0.3.2 readr_1.3.1 tidyr_0.8.3 tibble_2.1.2 #> [9] ggplot2_3.1.1 tidyverse_1.2.1 #> #> loaded via a namespace (and not attached): #> [1] tidyselect_0.2.5 xfun_0.7 haven_2.1.0 #> [4] lattice_0.20-38 colorspace_1.4-1 generics_0.0.2 #> [7] htmltools_0.3.6 yaml_2.2.0 rlang_0.3.4 #> [10] e1071_1.7-1 pillar_1.4.1 glue_1.3.1 #> [13] withr_2.1.2 DBI_1.0.0 modelr_0.1.4 #> [16] readxl_1.3.1 plyr_1.8.4 munsell_0.5.0 #> [19] gtable_0.3.0 cellranger_1.1.0 rvest_0.3.4 #> [22] evaluate_0.14 knitr_1.23 class_7.3-15 #> [25] highr_0.8 broom_0.5.2 Rcpp_1.0.1 #> [28] KernSmooth_2.23-15 scales_1.0.0 backports_1.1.4 #> [31] classInt_0.3-3 jsonlite_1.6 hms_0.4.2 #> [34] digest_0.6.19 stringi_1.4.3 grid_3.6.0 #> [37] cli_1.1.0 tools_3.6.0 magrittr_1.5 #> [40] lazyeval_0.2.2 crayon_1.3.4 pkgconfig_2.0.2 #> [43] xml2_1.2.0 lubridate_1.7.4 assertthat_0.2.1 #> [46] rmarkdown_1.13 httr_1.4.0 R6_2.4.0 #> [49] units_0.6-3 nlme_3.1-139 compiler_3.6.0 ```
@paleolimbot paleolimbot added bug an unexpected problem or unintended behavior coord 🗺️ labels Jun 17, 2019
@paleolimbot
Copy link
Member

@thomasp85 and/or @clauswilke - is the support for unicode in ggplot2/grid robust enough to just use the degree symbol rather than resort to plotmath?

@thomasp85
Copy link
Member

unicode support is a matter of the graphic device, not ggplot2/grid...

@thomasp85
Copy link
Member

To answer your question better - most devices support unicode characters, but using plotmath ensures that the symbol font is used and by that, that the degree sign glyph is present in the font...

@paleolimbot
Copy link
Member

The expression text is generated here in sf:

https://github.com/r-spatial/sf/blob/master/R/graticule.R#L220-L235

Surrounding the degree text in quotes when created seems safest. @edzer - if the degree_labels in a st_graticule() look like "0.4"*degree*W instead of 0.4*degree*W, would that cause problems elsewhere?

@clauswilke
Copy link
Member

As far as I can tell, this particular problem arises because ggplot creates a plotmath expression, then converts it into a string, and then parses the string again. At that parsing stage, the number with a comma as separator is not understood. I don't remember now why I wrote the code this way, but there was a valid reason for it. I think it's because we build an internal data frame that holds x and y labels in the same column, and it is possible that only one of the two sets of labels uses plotmath.

One general solution may be to store everything as plotmath, rather than everything as strings. Turning numbers into strings may also work. Finally, if the R locale settings are changed so it understands a comma as a separator (I assume this is possible, I never use it) things should also work.

@clauswilke
Copy link
Member

Relevant sections in the code:

ggplot2/R/coord-sf.R

Lines 80 to 83 in b560662

# all labels need to be temporarily stored as character vectors,
# but expressions need to be parsed afterwards
needs_parsing[graticule$type == "E"] <- !(is.character(x_labels) || is.factor(x_labels))
x_labels <- as.character(x_labels)

ggplot2/R/coord-sf.R

Lines 116 to 123 in b560662

# Parse labels if requested/needed
has_degree <- grepl("\\bdegree\\b", graticule$degree_label)
needs_parsing <- needs_parsing | (needs_autoparsing & has_degree)
if (any(needs_parsing)) {
labels <- as.list(graticule$degree_label)
labels[needs_parsing] <- parse_safe(graticule$degree_label[needs_parsing])
graticule$degree_label <- labels
}

@clauswilke
Copy link
Member

A workaround for now is to either set the labels manually or to provide functions that generate labels that R can parse later on.

library(ggplot2)
library(sf)
#> Linking to GEOS 3.6.1, GDAL 2.1.3, PROJ 4.9.3

nc <- st_read(system.file("shape/nc.shp", package = "sf"))
#> Reading layer `nc' from data source `/Library/Frameworks/R.framework/Versions/3.6/Resources/library/sf/shape/nc.shp' using driver `ESRI Shapefile'
#> Simple feature collection with 100 features and 14 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
#> epsg (SRID):    4267
#> proj4string:    +proj=longlat +datum=NAD27 +no_defs

degreeLabelsNS <- function(x) {
  pos = sign(x) + 2
  dir = c("*S", "", "*N")
  ggplot2:::parse_safe(paste0('"', abs(x), '"', "*degree", dir[pos]))
}

degreeLabelsEW <- function(x) {
  x <- ifelse(x > 180, x - 360, x)
  pos = sign(x) + 2
  if (any(x == -180))
    pos[x == -180] = 2
  if (any(x == 180))
    pos[x == 180] = 2
  dir = c("*W", "", "*E")
  ggplot2:::parse_safe(paste0('"', abs(x), '"', "*degree", dir[pos]))
}

options(OutDec = ",")

ggplot() +
  geom_sf(data = nc) +
  scale_x_continuous(
    labels = degreeLabelsEW
  ) +
  scale_y_continuous(
    labels = degreeLabelsNS
  )

Created on 2019-06-17 by the reprex package (v0.3.0)

@clauswilke
Copy link
Member

It might make sense to add these helper functions to the scales package, which holds all sorts of similar functions, and to make them more configurable (number of decimal places shown, etc.). We can't change the source code of sf every time we want the labels to look a little different.

@clauswilke
Copy link
Member

Just for completeness, here is the variation with a unicode degree symbol. If we provide these helper functions in scales, they could take an option to either generate plotmath or strings with unicode.

library(ggplot2)
library(sf)
#> Linking to GEOS 3.6.1, GDAL 2.1.3, PROJ 4.9.3

nc <- st_read(system.file("shape/nc.shp", package = "sf"))
#> Reading layer `nc' from data source `/Library/Frameworks/R.framework/Versions/3.6/Resources/library/sf/shape/nc.shp' using driver `ESRI Shapefile'
#> Simple feature collection with 100 features and 14 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
#> epsg (SRID):    4267
#> proj4string:    +proj=longlat +datum=NAD27 +no_defs

degreeLabelsNS <- function(x) {
  pos = sign(x) + 2
  dir = c("S", "", "N")
  paste0(abs(x), "°", dir[pos])
}

degreeLabelsEW <- function(x) {
  x <- ifelse(x > 180, x - 360, x)
  pos = sign(x) + 2
  if (any(x == -180))
    pos[x == -180] = 2
  if (any(x == 180))
    pos[x == 180] = 2
  dir = c("W", "", "E")
  paste0(abs(x), "°", dir[pos])
}

options(OutDec = ",")

ggplot() +
  geom_sf(data = nc) +
  scale_x_continuous(
    labels = degreeLabelsEW
  ) +
  scale_y_continuous(
    labels = degreeLabelsNS
  )

Created on 2019-06-17 by the reprex package (v0.3.0)

@edzer
Copy link
Contributor

edzer commented Jun 17, 2019

The advantage of improving these functions in sf would be that they'd also work for base plots, we get less duplication. @paleolimbot I'm fine if there is no further fallout from this change (haven't tried).

@clauswilke
Copy link
Member

@edzer My main point was that in the general case, we want to have these functions exported to the user and configurable. We may want to specify number of decimals, whether or not there's a space between the number and the degree symbol (as in 35 °N), etc. See dollar_format() and dollar() as examples: https://rdrr.io/cran/scales/man/dollar_format.html It fits into scales, but of course you could also add similar functions to sf.

@paleolimbot
Copy link
Member

Regardless of the home for alternative graticule calculations, I think it's reasonable to have coord_sf() defaults work regardless of locale. I'll let @edzer have a look at the PR to sf to see if this this will cause problem elsewhere.

In ggplot2, it causes these expectations to fail:

graticule <- b$layout$panel_params[[1]]$graticule
parsed <- vector("list", 3)
parsed[1:3] <- parse(text = c("10*degree*E", "20*degree*E", "30*degree*E"))
expect_identical(
graticule[graticule$type == "E", ]$degree_label,
parsed
)
parsed[1:3] <- parse(text = c("10*degree*N", "15*degree*N", "20*degree*N"))
expect_identical(
graticule[graticule$type == "N", ]$degree_label,
parsed
)
})

I think it won't be hard to find another way to test this.

@dicorynia
Copy link
Author

Thanks for this fast evaluation and workaround.

@lock
Copy link

lock bot commented Dec 28, 2019

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/

@lock lock bot locked and limited conversation to collaborators Dec 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug an unexpected problem or unintended behavior coord 🗺️
Projects
None yet
5 participants