Соответствующая процедура для использования group_by() %>% summary() %>% mutate() с объектами sf

#r #dplyr #tidyverse #sf

#r #dplyr #tidyverse #sf

Вопрос:

Я разрабатываю сценарий обработки рабочего процесса для работы с sf объектами в R. sf — класс объектов simple features, которые предоставляют средства обработки пространственных данных в tidyverse. Однако у меня возникают серьезные трудности при выполнении стандартных процессов group_by() %>% summary() %>% mutate() с данными, хранящимися как sf . Я сталкиваюсь с проблемой, когда group_by() %>% summary() работает с объектом после его преобразования во фрейм данных, но не как sf .

По сути, я пытаюсь сгруппировать географические области более низкого уровня по географическим областям более высокого уровня и вывести итоговые переменные. Затем мне нужно изменить переменную в моем новом sf объекте суммированных данных, который вычисляет сумму по нескольким переменным и делит на другую переменную. С sf объектами эта последняя операция выдает ошибку «x ‘x’ должно быть числовым», но идентичная операция работает для фрейма данных с теми же данными (только без sans geography ). И я проверил, что x является числовым для всех переменных, передаваемых в rowSums функцию.

Полное повторение ниже. В первом примере вы видите, что операция завершается с sf ошибкой для версии образца данных. Во втором примере с as.data.frame() передачей перед separate() функцией процесс завершается успешно, но это устраняет географические данные, которые имеют решающее значение для моего анализа.

Всем спасибо!

 library(sf)
#> Warning: package 'sf' was built under R version 4.0.2
#> Linking to GEOS 3.8.1, GDAL 3.1.1, PROJ 6.3.1
library(tidyverse)
#> Warning: package 'ggplot2' was built under R version 4.0.2
#> Warning: package 'tibble' was built under R version 4.0.2
#> Warning: package 'tidyr' was built under R version 4.0.2
#> Warning: package 'dplyr' was built under R version 4.0.2
library(dplyr)
library(spdep)
#> Loading required package: sp
#> Loading required package: spData
#> To access larger datasets in this package, install the spDataLarge
#> package with: `install.packages('spDataLarge',
#> repos='https://nowosad.github.io/drat/', type='source')`
library(stringi)
#> Warning: package 'stringi' was built under R version 4.0.2

nc <- st_read(system.file("shapes/sids.shp", package="spData")[1], quiet=TRUE)
st_crs(nc) <- " proj=longlat  datum=NAD27"
row.names(nc) <- as.character(nc$FIPSNO)

names(nc)
#>  [1] "CNTY_ID"   "AREA"      "PERIMETER" "CNTY_"     "NAME"      "FIPS"     
#>  [7] "FIPSNO"    "CRESS_ID"  "BIR74"     "SID74"     "NWBIR74"   "BIR79"    
#> [13] "SID79"     "NWBIR79"   "east"      "north"     "x"         "y"        
#> [19] "lon"       "lat"       "L_id"      "M_id"      "geometry"

nc %>% 
  separate(CNTY_ID,into = c("ID1","ID2"),sep = 2,remove = FALSE) %>% 
  group_by(ID1) %>% 
  dplyr::summarize(AREA = sum(AREA, na.rm = TRUE), 
                   BIR74 = sum(BIR74,na.rm = TRUE), 
                   SID74 = sum(SID74,na.rm = TRUE), 
                   NWBIR74 = sum(NWBIR74,na.rm = TRUE)
                   ) %>% 
  mutate(stupid_var = rowSums(dplyr::select(.,'SID74':'NWBIR74'))/BIR74)
#> `summarise()` ungrouping output (override with `.groups` argument)
#> Error: Problem with `mutate()` input `stupid_var`.
#> x 'x' must be numeric
#> ℹ Input `stupid_var` is `rowSums(dplyr::select(., "SID74":"NWBIR74"))/BIR74`.

class(nc$SID74)
#> [1] "numeric"
class(nc$NWBIR74)
#> [1] "numeric"
class(nc$BIR74)
#> [1] "numeric"

nc %>% 
  as.data.frame() %>% 
  separate(CNTY_ID,into = c("ID1","ID2"),sep = 2,remove = FALSE) %>% 
  group_by(ID1) %>% 
  dplyr::summarize(AREA = sum(AREA, na.rm = TRUE), 
                   BIR74 = sum(BIR74,na.rm = TRUE), 
                   SID74 = sum(SID74,na.rm = TRUE), 
                   NWBIR74 = sum(NWBIR74,na.rm = TRUE)
  ) %>% 
  mutate(stupid_var = rowSums(dplyr::select(.,'SID74':'NWBIR74'))/BIR74)
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 5 x 6
#>   ID1    AREA  BIR74 SID74 NWBIR74 stupid_var
#>   <chr> <dbl>  <dbl> <dbl>   <dbl>      <dbl>
#> 1 18    2.53   36723    89   12788      0.351
#> 2 19    4.03  132525   203   38392      0.291
#> 3 20    3.94  111540   237   35281      0.318
#> 4 21    1.63   38117   106   14915      0.394
#> 5 22    0.494  11057    32    3723      0.340
  

Создано 2020-09-21 пакетом reprex (версия 0.3.0)

Ответ №1:

Я внес изменения в следующую строку кода.

мутировать(stupid_var = rowSums(dplyr::select(.,'SID74':'NWBIR74'))/BIR74)

Эта строка кода, вероятно, вызывала проблему. Если я чего-то не упустил, может показаться, что нет причин для суммирования целых столбцов для каждой строки. Итак, код был изменен, чтобы удалить функцию rowSums() . Функция mutate по-прежнему использовалась для выполнения математических вычислений на основе данных в каждой строке данных, но без использования каких-либо значений rowSums() .

 p1 <- nc %>% 
  separate(CNTY_ID,into = c("ID1","ID2"),sep = 2,remove = FALSE) %>% 
               group_by(ID1)  %>% 
               dplyr::summarize(AREA = sum(AREA, na.rm = TRUE), 
               BIR74 = sum(BIR74,na.rm = TRUE), 
               SID74 = sum(SID74,na.rm = TRUE), 
               NWBIR74 = sum(NWBIR74,na.rm = TRUE)) %>%
               mutate( stupid_var = ( (p2$SID74)   (p2$NWBIR74)) / (p2$BIR74) )
p1
  

Результат можно просмотреть по этой ссылке.

Комментарии:

1. Большое вам спасибо за это решение. Наверное, я неправильно понял, как работает rowSums. Другая проблема здесь заключается в том, что в моих реальных данных мне нужно суммировать диапазон из множества значений переменных для числителя stupid_var , что делает использование a между каждым запутанным и громоздким. Можете ли вы изменить решение так, чтобы оно представляло собой диапазон именованных значений?

Ответ №2:

Вероятно, есть какая-то причина, по которой city_ID был разделен на 2 переменные, но вы не предоставили никаких указаний на причину. В первом ответе я сделал разделение, но я игнорирую использование этих разделенных переменных здесь.

Всякий раз, когда данные включают столбец геометрии sf, эта геометрия sf является липкой и будет следовать за данными. Даже когда данные получают подмножество. И когда эта геометрия sf присутствует, это вызывает проблемы с базовыми функциями столбцов или строк, такими как sum() . Таким образом, эта геометрия должна быть удалена до того, как будет использована функция sum .

В этом втором ответе я использовал те же две переменные, которые использовались в ответе № 1. Данные nc получают подмножество для столбцов 8 и 9. Мой выбор, потому что нет указаний о том, какие столбцы добавляются вместе. Затем геометрия sf удаляется, а затем функция rowSums используется для добавления значений из каждого столбца для каждой строки.

 gr_1 <- nc[, c(9:10)]
gr_1 <- st_drop_geometry(gr_1)     
rownames(gr_1) = NULL           # to remove extraneous data from gr_1

xsum <- c(rowSums(gr_1))
head(xsum)                             # displays values of xsum
  

Вывод можно просмотреть по этой ссылке:

Комментарии:

1. Причина, по которой я разделил CNTY_ID на две переменные, заключалась в попытке воспроизвести проблему иерархической агрегации, с которой я столкнулся, с истинными данными (я просто использую эти встроенные данные NC для reprex). Мне нужно суммировать интересующие переменные на основе переменной идентификатора более высокого уровня (так что в моем случае районы группируют группы блоков в городах) различными способами, и я хочу также сохранить и сгруппировать географию в пределах этого более высокого географического уровня. Я использовал separate() функцию для разделения CNTYID, чтобы иметь вариации в ID1 переменной для группировки. Мои реальные данные большие и сложные.