¿Cómo extraer un número de una cadena en un marco de datos en R y colocarlo en una nueva columna?
dataframe (6)
¿Por qué no una solución base R?
df$new <- as.numeric(gsub("[^[:digit:]]+", "", df$test))
df
# test value new
#1 test_A_1.txt 0.51 1
#2 test_A_2.txt 0.52 2
#3 test_A_3.txt 0.56 3
Editar.
Siguiendo el ejemplo de la
answer
user @ camille donde las cadenas pueden tener diferentes números de números, aquí hay una solución que usa el paquete
stringr
.
df1 <- data.frame(test = c("test_A_1.txt", "test_A_2.txt", "test_A_3.txt"), value = c(0.51, 0.52, 0.56))
df2 <- data.frame(test = c("test_A_1_1.txt", "test_A_2_1.txt", "test_A_3_1.txt"), value = c(0.51, 0.52, 0.56))
df3 <- data.frame(test = c("test_A_1_1.txt", "test_A_2_1.txt", "test_A_3_1.txt", "test_A_4_2_1.txt"), value = c(0.51, 0.52, 0.56, 2))
num2cols <- function(DF, col = "test"){
s <- stringr::str_extract_all(DF[[col]], "[[:digit:]]+")
Max <- max(sapply(s, length))
new <- do.call(rbind, lapply(s, function(x){
as.numeric(c(x, rep(NA, Max - length(x))))
}))
names_new <- paste0("new", seq.int(ncol(new)))
setNames(cbind(DF, new), c(names(DF), names_new))
}
num2cols(df1)
num2cols(df2)
num2cols(df3)
Tengo un marco de datos simple:
df <- data.frame(test = c("test_A_1_1.txt", "test_A_2_1.txt", "test_A_3_1.txt"), value = c(0.51, 0.52, 0.56))
test value
1 test_A_1_1.txt 0.51
2 test_A_2_1.txt 0.52
3 test_A_3_1.txt 0.56
Rendimiento esperado
Me gustaría copiar los números al final de la cadena en la columna 1 y colocarlos en la columna tres o cuatro respectivamente, así:
test value new new
1 test_A_1.txt 0.51 1 1
2 test_A_2.txt 0.52 2 1
3 test_A_3.txt 0.56 3 1
Intento
Usando el siguiente código, puedo extraer los números de la cadena:
library(stringr)
as.numeric(str_extract_all("test_A_3.txt", "[0-9]+")[[1]])[1] # Extracts the first number
as.numeric(str_extract_all("test_A_3.txt", "[0-9]+")[[1]])[2] # Extracts the second number
Me gustaría aplicar este código en todos los valores de la primera columna:
library(tidyverse)
df %>% mutate(new = as.numeric(str_extract_all(df$test, "[0-9]+")[[1]])[1])
Sin embargo, esto lleva a una columna
new
, con solo el número
1
.
¿Qué estoy haciendo mal?
Al ver que usted dijo que podría tener varios números en el nombre de un archivo, sugeriría usar un método más detallado pero que se pueda escalar para trabajar con más de 1 o 2 números.
De esa manera, no se están codificando columnas como
new2
y
new2
.
Para ilustrar, agregué un tercer número a uno de los nombres de archivo.
El problema original con el que se encontró fue que
str_extract_all
devuelve una lista, y luego necesita extraer elementos de esa lista.
Puede anular la lista para obtener filas individuales para cada número, agregar una clave que secuencia sobre los números de cada nombre de archivo, luego expandirse a una forma amplia para obtener una columna por número, con
NA
donde no existe un número en el nombre del archivo.
library(dplyr)
library(stringr)
library(tidyr)
df <- data.frame(test = c("test_A_1_1.txt", "test_A_2_1.txt", "test_A_3_1.txt", "test_A_4_2_1.txt"), value = c(0.51, 0.52, 0.56, 2))
df %>%
mutate(nums = str_extract_all(test, "//d+")) %>%
unnest(nums) %>%
group_by(test) %>%
mutate(key = row_number()) %>%
spread(key, value = nums, sep = "")
#> # A tibble: 4 x 5
#> # Groups: test [4]
#> test value key1 key2 key3
#> <fct> <dbl> <chr> <chr> <chr>
#> 1 test_A_1_1.txt 0.51 1 1 <NA>
#> 2 test_A_2_1.txt 0.52 2 1 <NA>
#> 3 test_A_3_1.txt 0.56 3 1 <NA>
#> 4 test_A_4_2_1.txt 2 4 2 1
Dado que son de ancho fijo, puedes:
df$new <- substr(df$test, 8, 8) %>% as.integer
Recomiendo usar
as.integer
lugar de
as.numeric
porque está trabajando con enteros, no con flotantes.
Modificando ligeramente su código existente:
df %>%
mutate(new = as.integer(str_extract(test, "[0-9]+")))
O simplemente
df$new <- as.integer(str_extract(df$test, "[0-9]+"))
Podemos usar
parse_number
desde
readr
library(dplyr)
library(purrr)
library(stringr)
df %>%
mutate(new = readr::parse_number(as.character(test)))
Con respecto al problema del OP, está seleccionando solo el primer elemento de la
list
(
[[1]]
) del
str_extract_all
(que devuelve una
list
).
En su lugar, es mejor usar
str_extract
ya que necesitamos extraer solo la primera instancia de uno o más dígitos (
//d+
)
df %>%
mutate(new = as.numeric(str_extract(test, "[0-9]+")))
Si necesitamos obtener la salida de
str_extract_all
(en el caso),
unlist
la
list
en un
vector
y luego aplique el
as.numeric
en ese
vector
df %>%
mutate(new = as.numeric(unlist(str_extract_all(test, "[0-9]+"))))
Si hay varias instancias, entonces manténgala como una
list
después de convertirla en
numeric
haciendo un bucle a través de los elementos de la
list
con
map
df %>%
mutate(new = map(str_extract_all(test, "[0-9]+"), as.numeric))
NOTA: La solución basada en
str_extract
se publicó por primera vez aquí.
En la
base R
, podemos usar
regexpr
df$new <- as.numeric(regmatches(df$test, regexpr("//d+", df$test)))
Actualizar
Con el ejemplo actualizado, si necesitamos obtener dos instancias de números, el primero se puede extraer con
str_extract
y el último (
stri_extract_last
- from
stringi
se puede usar), proporcionando un aspecto de expresión regular para verificar los dígitos seguidos por un
.
y ''txt''
df %>%
mutate(new1 = as.numeric(str_extract(test, "//d+")),
new2 = as.numeric(str_extract(test, "//d+(?=//.txt)")))
# test value new1 new2
#1 test_A_1_1.txt 0.51 1 1
#2 test_A_2_1.txt 0.52 2 1
#3 test_A_3_1.txt 0.56 3 1
También podemos usar
sub
o
stringi::stri_extract_last_regex
:
sapply(df1, function(x) sub(''.*(//d{1}).*'', ''//1'', x))
o
sapply(df1, function(x) stringi::stri_extract_last_regex(x, "//d{1}"))