Я хотел выполнить некоторые манипуляции со строками на основе условия длины символов.
У меня есть эта таблица, назовем ее образцом таблицы.
Я хочу преобразовать столбец RiskCode в примерной таблице, создав новый столбец, например этот:
Я создал функцию для преобразования строки:
icd_transform <- function(x){
if (nchar(x) > 6) {
return(substr(x,1,6))
} else if (nchar(x) == 5) {
return(paste(x,"0",sep = ""))
} else if (nchar(x) == 3) {
return(paste(x,".00",sep = ""))
} else {return(x)}
}
Я попытался использовать функцию выше при применении, чтобы сначала увидеть результаты.
apply(sample$RiskCode,2,icd_transform)
Но я нашел ошибку ниже:
Error in apply(sample$RiskCode, 2, icd_transform) :
dim(X) must have a positive length
Ребята, не могли бы вы помочь мне решить проблему? Спасибо.
sapply (х, icd_transform). Apply не используется для векторов, но используется для матриц (также может использоваться для df).
К вашему сведению возврат() не нужен.
Более того, всегда ли ожидаемый формат «‹БУКВА><две цифры.‹три цифры>>? Потому что если это так, то логику можно выразить одним выражением без условных переходов через sprintf()
.
Вы можете напрямую изменить свою переменную, не создавая функцию:
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
sample <- data.frame(RiskCode = c("A01", "A02.999"))
sample <- mutate(sample,
RiskCode2 = case_when(
nchar(RiskCode) > 6 ~ substr(RiskCode, 1, 6),
nchar(RiskCode) == 5 ~ paste0(RiskCode, "0"),
nchar(RiskCode) == 3 ~ paste0(RiskCode, ".00")
))
print(sample)
#> RiskCode RiskCode2
#> 1 A01 A01.00
#> 2 A02.999 A02.99
Created on 2024-07-19 with reprex v2.1.0
Подход с использованием Vectorize
sample$RiskCode2 <- Vectorize(\(x) icd_transform(x))(sample$RiskCode)
sample
RiskCode RiskCode2
1 A01 A01.00
2 A02.999 A02.99
Vectorize(\(x) icd_transform(x))
=> Vectorize(icd_transform)
.
Более подробная версия imo помогает понять природу функции Vectorize и показывает связь между анонимной функцией и аргументом.
Об этом уже говорилось в комментариях, но я решил расширить эти комментарии.
apply(X, 2, ...)
должен X
быть матрицей или другим объектом как минимум с двумя измерениями (в этом случае он вызывает функцию один раз для каждого столбца), но в коде вопроса X
представляет собой простой вектор (который вообще не имеет измерений).
dim(sample$RiskCode)
## NULL
1) Вместо этого мы можем использовать sapply
, который перебирает компоненты вектора (или списка). Это будет работать с icd_transform
, как указано в вопросе.
sapply(sample$RiskCode, icd_transform)
Функция Vectorize
, упомянутая в другом ответе, также будет работать.
2) или мы могли бы переписать icd_transform
, чтобы принимать векторы, используя ifelse
вместо if...else...
, и в этом случае нам даже не понадобится sapply
. При этом создается новый фрейм данных sample2 с результатом. Здесь x
может быть вектором. Обратите внимание, что strtrim(x, 6)
вернет x
без изменений, если x
содержит меньше или равно 6 символов, а в противном случае усекает его до 6 символов.
icd_transform2 <- function(x) {
suffix <- ifelse(nchar(x) == 5, "0",
ifelse(nchar(x) == 3, ".00", ""))
x |>
strtrim(6) |>
paste0(suffix)
}
sample2 <- sample |> transform(RiskCode2 = icd_transform2(RiskCode))
3) В пакете dplyr есть полезный case_match
, который обеспечивает векторизованный многопозиционный переключатель.
library(dplyr)
icd_transform3 <- function(x) {
case_match(nchar(x),
3 ~ paste0(x, ".00"),
5 ~ paste0(x, "0"),
.default = strtrim(x, 6))
}
sample2 <- sample |> mutate(RiskCode2 = icd_transform3(RiskCode))
4) Один комментарий касается использования sprintf
, и в зависимости от общего случая вы можете использовать что-то вроде этого:
library(dplyr)
icd_transform4 <- function(x) {
sprintf("%s%05.2f", strtrim(x, 1), as.numeric(substring(strtrim(x, 6), 2)))
}
sample |>
mutate(RiskCode2 = icd_transform4(RiskCode))
Хотя это не обязательно будет полезно для ответа на этот вопрос, вас может заинтересовать пакет icd на github. Если в Windows сначала установите Rtools. (Rtools содержит компилятор C и другие инструменты, необходимые для сборки icd.)
library(devtools)
install_github("jackwasey/icd")
Ввод в воспроизводимой форме:
sample <- data.frame(RiskCode = c("A01", "A02.999"))
Еще один tidyverse
вариант:
# Pkgs (dplyr, stringr) ---------------------------------------------------
library(tidyverse)
# Sample data -------------------------------------------------------------
my_df <- structure(
list(risk_code = c("A01", "A02.999")),
class = c("spec_tbl_df", "tbl_df", "tbl", "data.frame"),
row.names = c(NA, -2L))
# Code (just paste0 and str_sub) ------------------------------------------
my_df <- mutate(
my_df,
new_risk_code = str_sub(
paste0(
risk_code, # "A00.00" is the
str_sub("A00.00", str_length(risk_code) + 1, 6)), # default pattern
1, 6)) # with length 6
# Output ------------------------------------------------------------------
print(my_df)
#> # A tibble: 2 × 2
#> risk_code new_risk_code
#> <chr> <chr>
#> 1 A01 A01.00
#> 2 A02.999 A02.99
Created on 2024-07-19 with reprex v2.1.0
В базе R вы можете использовать regex
+ sprintf
. Все векторизовано:
a <- strcapture("(\\D+)(\\d{2}).?(\\d{2})?", df$risk_code, list("", 0,0))
do.call(sprintf, c("%s%02d.%02d", replace(a, is.na(a), 0)))
[1] "A01.00" "A02.99"
Другой вариант — пакет gsubfn
.
fn <- function(x, y, z){
y <- ifelse(nzchar(y), as.numeric(y), 0)
z <- ifelse(nzchar(z), as.numeric(z), 0)
sprintf("%s%02d.%02d", x, y, z)
}
gsubfn::gsubfn("(\\D+)(\\d{2}).?(\\d{2})?.*",fn, df$risk_code)
[1] "A01.00" "A02.99"
@Edward Извините, я обновляю вопрос, исправил ошибку «условие имеет длину> 1».