Манипулирование строками на основе длины символа в фрейме данных

Я хотел выполнить некоторые манипуляции со строками на основе условия длины символов.

У меня есть эта таблица, назовем ее образцом таблицы.

Код риска А01 А02.999

Я хочу преобразовать столбец RiskCode в примерной таблице, создав новый столбец, например этот:

Код риска Код риска2 А01 А01.00 А02.999 А02.99

Я создал функцию для преобразования строки:

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

Ребята, не могли бы вы помочь мне решить проблему? Спасибо.

@Edward Извините, я обновляю вопрос, исправил ошибку «условие имеет длину> 1».

Dhestar Bagus Wirawan 19.07.2024 11:18

sapply (х, icd_transform). Apply не используется для векторов, но используется для матриц (также может использоваться для df).

s_baldur 19.07.2024 11:22

К вашему сведению возврат() не нужен.

Konrad Rudolph 19.07.2024 14:50

Более того, всегда ли ожидаемый формат «‹БУКВА><две цифры.‹три цифры>>? Потому что если это так, то логику можно выразить одним выражением без условных переходов через sprintf().

Konrad Rudolph 19.07.2024 14:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
90
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы можете напрямую изменить свою переменную, не создавая функцию:

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).
Konrad Rudolph 19.07.2024 14:50

Более подробная версия imo помогает понять природу функции Vectorize и показывает связь между анонимной функцией и аргументом.

Andre Wildberg 19.07.2024 15:14
Ответ принят как подходящий

Об этом уже говорилось в комментариях, но я решил расширить эти комментарии.

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"

Другие вопросы по теме