2012-06-20 17 views
5

Tengo un archivo de datos de texto que probablemente leeré con readLines. La porción inicial de cada cadena contiene muchos galimatías seguidos de los datos que necesito. El galimatías y los datos generalmente están separados por tres puntos. Me gustaría dividir las cuerdas después de los últimos tres puntos, o reemplazar los últimos tres puntos con un marcador de algún tipo que le indique a R que trate todo a la izquierda de esos tres puntos como una columna.R: eliminación de los últimos tres puntos de una cadena

Aquí está un puesto similar en Stackoverflow que se localice el último punto:

R: Find the last dot in a string

Sin embargo, en mi caso algunos de los datos tienen decimales, por lo que la localización del último punto no será suficiente. Además, creo que ... tiene un significado especial en R, lo que podría estar complicando el problema. Otra posible complicación es que algunos de los puntos son más grandes que otros. Además, en algunas líneas, uno de los tres puntos fue reemplazado por una coma.

Además de gregexpr en la publicación anterior, he intentado utilizar gsub, pero no puedo encontrar la solución.

Aquí es establecer un conjunto de datos de ejemplo y el resultado que esperan lograr:

aa = matrix(c(
'first string of junk... 0.2 0 1', 
'next string ........2 0 2', 
'%%%... ! 1959 ... 0 3 3', 
'year .. 2 .,. 7 6 5', 
'this_string is . not fine .•. 4 2 3'), 
nrow=5, byrow=TRUE, 
dimnames = list(NULL, c("C1"))) 

aa <- as.data.frame(aa, stringsAsFactors=F) 
aa 

# desired result 
#        C1 C2 C3 C4 
# 1  first string of junk 0.2 0 1 
# 2   next string ..... 2 0 2 
# 3    %%%... ! 1959  0 3 3 
# 4     year .. 2  7 6 5 
# 5 this_string is . not fine 4 2 3 

espero que esta cuestión no se considera demasiado específica. El archivo de datos de texto se creó utilizando los pasos descritos en mi publicación de ayer sobre la lectura de un archivo MSWord en R.

Algunas líneas no contienen galimatías o tres puntos, sino solo datos. Sin embargo, eso podría ser una complicación para una publicación de seguimiento.

Gracias por cualquier consejo.

+0

¿Puedes hacer primero una búsqueda y reemplazar todas las comas y los puntos grandes en puntos regulares? –

+0

No creo que pueda reemplazar las comas con puntos porque los datos contienen comas en los números más grandes: 4,500. Olvidé mencionar eso en mi publicación. Aunque tal vez podría reemplazar las comas con puntos y luego eliminar los puntos de los datos después de eliminar el galimatías. –

Respuesta

5

Esto hace el truco, aunque no especialmente elegante ...

options(stringsAsFactors = FALSE) 


# Search for three consecutive characters of your delimiters, then pull out 
# all of the characters after that 
# (in parentheses, represented in replace by \\1) 
nums <- as.vector(gsub(aa$C1, pattern = "^.*[.,•]{3}\\s*(.*)", replace = "\\1")) 

# Use strsplit to break the results apart at spaces and just get the numbers 
# Use unlist to conver that into a bare vector of numbers 
# Use matrix(, nrow = length(x)) to convert it back into a 
# matrix of appropriate length 
num.mat <- do.call(rbind, strsplit(nums, split = " ")) 


# Mash it back together with your original strings 
result <- as.data.frame(cbind(aa, num.mat)) 

# Give it informative names 
names(result) <- c("original.string", "num1", "num2", "num3") 
+0

Vale la pena señalar que el 'gran punto' me dio problemas cuando intenté enviar este código desde Vim; sin embargo, cuando se copia desde el sitio web, funciona bien. Así que mi flujo fue editar en Vim, pegar en el sitio web y luego pegar en mi consola ... eso no está bien. –

+0

Parece que tal vez el código asigna los números 4,2,3 (desde la última cadena) a las 5 cadenas del conjunto de datos. –

+0

@MarkMiller Ah, lo siento, estaba trabajando con la matriz 'aa', no como un data.frame. Si desea utilizar un data.frame, puede asignar 'nums' de esta manera:' as.vector (gsub (aa $ C1, pattern = "^. * [., •] {3} \\ s * (. *) ", replace =" \\ 1 "))' –

0

Invertir la cadena
Invertir el patrón que está buscando, si es necesario - no es en su caso
Invertir el resultado

[haiku-pseudocódigo]

a = 'first string of junk... 0.2 0 1' // string to search 
b = 'junk' // pattern to match 

ra = reverseString(a) // now equals '1 0 2.0 ...knuj fo gnirts tsrif' 
rb = reverseString (b) // now equals 'knuj' 

// run your regular expression search/replace - search in 'ra' for 'rb' 
// put the result in rResult 
// and then unreverse the result 
// apologies for not knowing the syntax for 'R' regex 

[/ haiku-pseudocode]

+1

¿Es esto un haiku? Aw, no importa ... todavía no lo entiendo. – GSee

+0

Ver la edición de haiku – KevinDTimm

2

Esto le dará la mayor parte del camino, y no tendrá problemas con números que incluyen comas:

# First, use a regex to eliminate the bad pattern. This regex 
# eliminates any three-character combination of periods, commas, 
# and big dots (•), so long as the combination is followed by 
# 0-2 spaces and then a digit. 
aa.sub <- as.matrix(
    apply(aa, 1, function (x) 
    gsub('[•.,]{3}(\\s{0,2}\\d)', '\\1', x, perl = TRUE))) 

# Second: it looks as though you want your data split into columns. 
# So this regex splits on spaces that are (a) preceded by a letter, 
# digit, or space, and (b) followed by a digit. The result is a 
# list, each element of which is a list containing the parts of 
# one of the strings in aa. 
aa.list <- apply(aa.sub, 1, function (x) 
    strsplit(x, '(?<=[\\w\\d\\s])\\s(?=\\d)', perl = TRUE)) 

# Remove the second element in aa. There is no space before the 
# first data column in this string. As a result, strsplit() split 
# it into three columns, not 4. That in turn throws off the code 
# below. 
aa.list <- aa.list[-2] 

# Make the data frame. 
aa.list <- lapply(aa.list, unlist) # convert list of lists to list of vectors 
aa.df <- data.frame(aa.list)  
aa.df <- data.frame(t(aa.df), row.names = NULL, stringsAsFactors = FALSE) 

Lo único que queda es modificar la expresión regular para strsplit() para que puede manejar la segunda cadena en aa. O tal vez es mejor simplemente manejar casos como ese manualmente.

+0

Si agrego un espacio entre el último punto y el 2 en la segunda cadena, podría modificar la línea aa.list para manejarlo? En mis datos reales, creo que siempre hubo un espacio después del último punto y simplemente no me di cuenta cuando creé 'aa'. También puedo tratar de descubrir cómo modificar aa.list. –

+0

Sí, si agrega un espacio entre el último punto y el de la segunda cadena, la expresión regular en el segundo paso podría modificarse para manejar esa cadena. Es un poco complicado, pero factible. Dicho esto, creo que @MattParker tiene una mejor idea: comience separando cada una de sus cadenas en una parte "mala" (primera columna) y una parte que se porta bien (columnas de datos). Luego aplique expresiones regulares a la primera columna. Luego vuelve a unir las dos partes. Si lo haces de esta manera, puedes mantener la expresión regular en 'strsplit' bastante simple. De lo contrario, la expresión regular va a ser más compleja. – user697473

Cuestiones relacionadas