Преобразование смешанных единиц измерения

У меня есть файл с большим диапазоном нестандартизированных смешанных имперских и метрических измерений, который я хочу стандартизировать и опубликовать.

Образец этого диапазона выглядит так:

df  <- data.frame(Measurements =c("1.25m", "2 Feet", "3 Inches", "5.5 cm"))

|Measurements|
|1.25m       |
|2 Feet      |
|3 Inches    |
|5.5 cm      |

который я хочу выглядеть так:

|Measurements|MM_Conversion|
|1.25m       |1200mm
|2 Feet      |609.6mm
|3 Inches    |76.2mm
|5.5 cm      |55mm

Я не могу использовать measurements::conv_unit или units::set_unit, потому что они оба требуют числовых входных значений. Есть ли простой способ сделать это, который может анализировать как значение, так и строку и соответствующим образом преобразовывать?

EDIT 1: Возникла проблема, из-за которой Conv_Unit не может преобразовать значения NA. Если бы вместо этого начальный вектор был: df <- data.frame(Measurements =c(NA, 1.25m", "2 Feet", "3 Inches", "5.5 cm")), как бы вы его обошли?


person rsylatian    schedule 28.08.2018    source источник
comment
Один из способов, который я могу придумать, - это создать пользовательскую функцию, а затем использовать apply для преобразования всех измерений.   -  person SmitM    schedule 28.08.2018
comment
Я тоже об этом думал, но это становится громоздким, потому что есть 20 различных измерений.   -  person rsylatian    schedule 28.08.2018


Ответы (2)


Мы можем использовать extract из tidyr, чтобы разделить значение и единицу измерения и передать их в conv_unit, используя map2:

df <- data.frame(Measurements =c(NA, "1.25m", "2 Feet", "3 Inches", "5.5 cm"))

library(tidyverse)
library(stringr)
library(measurements)

df %>%
  extract(Measurements, c("value", "unit"), 
          regex = "^([\\d.]+)\\s*([[:alpha:]]+)$", 
          remove = FALSE, convert = TRUE) %>%
  mutate(unit = str_replace_all(unit, c(Feet="ft", Inches="inch")),
         MM_Conversion = paste0(map2(value, unit, ~if(!is.na(.x)) conv_unit(.x, .y, "mm") else NA), "mm"))

Результат:

  Measurements value unit MM_Conversion
1         <NA>    NA <NA>          NAmm
2        1.25m  1.25    m        1250mm
3       2 Feet  2.00   ft       609.6mm
4     3 Inches  3.00 inch        76.2mm
5       5.5 cm  5.50   cm          55mm

или используйте filter, если NAs не должны появляться в окончательном выводе:

df %>%
  extract(Measurements, c("value", "unit"), 
          regex = "^([\\d.]+)\\s*([[:alpha:]]+)$", 
          remove = FALSE, convert = TRUE) %>%
  filter(!is.na(Measurements)) %>%
  mutate(unit = str_replace_all(unit, c(Feet="ft", Inches="inch")),
         MM_Conversion = paste0(map2(value, unit, ~conv_unit(.x, .y, "mm")), "mm"))

Результат:

  Measurements value unit MM_Conversion
1        1.25m  1.25    m        1250mm
2       2 Feet  2.00   ft       609.6mm
3     3 Inches  3.00 inch        76.2mm
4       5.5 cm  5.50   cm          55mm

Обратите внимание, как я вручную сократил исходные единицы измерения, чтобы conv_unit работало. Было бы на один шаг меньше, если бы исходные единицы уже были в сокращенной форме.

person acylam    schedule 28.08.2018
comment
Элегантный ответ тоже, но что здесь делает функция map2? - person rsylatian; 28.08.2018
comment
функция conv_unit не векторизована, поэтому нам нужно сопоставить каждый элемент value и соответствующий unit с conv_unit. - person acylam; 28.08.2018
comment
Извиняюсь за то, что немного подвел меня, но скажем, например, вместо этого было Measurements = c(NA, "1.25m", "2 Feet", "3 Inches", "5.5 cm") , ответ прерывается. Найден неконвейерное решение, но не может заставить его работать с вашим ответом. Есть ли способ включить в него not_na <- !is.na(df$Measurements)? - person rsylatian; 29.08.2018
comment
@rsylatian Спасибо, что указали на это. Вы хотите, чтобы какие-либо NA строки остались NA или просто были удалены? - person acylam; 29.08.2018
comment
@rsylatian Смотрите мое обновление. Теперь он должен работать со значениями NA. Если вместо этого вы хотите удалить их из вывода, есть другое решение. - person acylam; 29.08.2018

это можно (легко) сделать, но сначала вы должны зафиксировать единицы измерения, так как принятые единицы длины из measurements::conv_unit

# accepted units
# $length
# [1] "angstrom" "nm"       "um"       "mm"       "cm"       "dm"       "m"        "km"       "inch"     "ft"       "yd"       "fathom"   "mi"       "naut_mi" 
# [15] "au"       "light_yr" "parsec"   "point" 

Итак, дюймы должны стать «дюймами», а «футы» должны стать «футами» (выполнить некоторую магию регулярных выражений ;-)).. но тогда...

library(tidyverse)
df  <- data.frame( Measurements =c( "1.25m", "2 ft", "3 inch", "5.5 cm" ) )

df %>% 
  #extract the numeric and the unit-parts from the string
  mutate( num_part = as.numeric( stringr::str_extract( Measurements, "\\d+\\.*\\d*" ) ), 
          unit_part = stringr::str_extract( Measurements, "[a-zA-Z]+" ) ) %>%
  #perform a rowwise operation
  rowwise() %>% 
  #convert the units to mm, row-by-row
  mutate( in_mm = conv_unit( num_part, unit_part, "mm" ) )

# Source: local data frame [4 x 4]
# Groups: <by row>
#   # A tibble: 4 x 4
#   Measurements num_part unit_part  in_mm
#   <fct>           <dbl> <chr>      <dbl>
# 1 1.25m            1.25 m         1250  
# 2 2 ft             2    ft         610. 
# 3 3 inch           3    inch        76.2
# 4 5.5 cm           5.5  cm          55  
person Wimpel    schedule 28.08.2018
comment
+1 Хорошее использование rowwise, но я стараюсь избегать его, насколько это возможно, так как это нелогично в смысле dplyr. - person acylam; 28.08.2018
comment
@avid_useR верно, но поскольку conv_unit нужны фиксированные единицы измерения, я решил срезать углы на этом ;-) - person Wimpel; 28.08.2018
comment
Элегантный ответ, никогда раньше не использовал rowWise. Просто чтобы я мог понять, что происходит, не могли бы вы объяснить, что именно «сгруппировано здесь» и как «фиксированные» единицы влияют на функцию «conv_unit»? Прокомментировал это и нашел сообщение об ошибке бесполезным. - person rsylatian; 28.08.2018
comment
@rsylatian rowwise выполняет операцию построчно... это необходимо, потому что conv_unit(x, from, to) нужно, чтобы from было строкой, а не вектором. В ответе @avid_useR эта проблема решена с помощью map. В этом случае каждая строка (поскольку я установил rowwise() ), conv_unit использует параметры x = num_part и from = unit_part из этой конкретной строки. - person Wimpel; 29.08.2018