2016-03-09 7 views
8

Scaricata una pila di dati da un odioso sito del governo locale. Ci sono 77.000 voci di elementi che appaiono esattamente come i seguenti, contenuti in un file di testo normale. Ho bisogno di importare questo mucchio di sterco in R come un frame di dati:Importazione di dati formattati in modo anomalo nel file di testo in R

Instrument: 201301240005447 
Recorded: 01/24/2013 
Consideration: $150,125.00 
Document Type: MORTGAGES 
Pages: 17 
Grantor: BYRES, CONNIE R/BYRES, SCOTT 
Grantee: MORTGAGE ELECTRONIC REGISTRATION SYSTEMS INC/QUICKEN LOANS INC 
Legal Description: * St:5495 MCNAMARA LN City:FLINT PrpId:1135532002 CC:11 T:8 R:7 S:35 ext:PT OF NE4 
* 
---------------------------------/--------------------------------- 
Instrument: 201301240005408 
Recorded: 01/24/2013 
Consideration: $65,124.00 
Document Type: MORTGAGES 
Pages: 17 
Grantor: SANNE, BETTY LOU/SANNE, KENNETH D 
Grantee: JPMORGAN CHASE BANK NA 
Legal Description: Sub:WOODCROFT NO 1 Lt:188 St:2213 RADCLIFFE AVE City:FLINT PrpId:4024106003 CC:54 
* 
---------------------------------/--------------------------------- 

ci sono vettori di caratteri comuni come "strumento", "Concedente", e "PrpId". Esattamente come lo importerei in R? Ciò comporterebbe l'analisi o la raschiatura di qualche tipo?

Inutile dire che ho provato a importare questo file in Excel ma non ha funzionato. Penso che R avrebbe funzionato molto meglio, solo bisogno di capire come. Grazie

+0

@rawr, non ha funzionato, continuo a ricevere questo tipo di errore: 'Problemi con yaml.load (pasta (readlines (ingresso), collasso = "\ n"), ...): Errore scanner: durante la scansione di un tasto semplice alla riga 13, la colonna 1non può trovare previsto ':' alla riga 14, colonna 1' Forse non è nel formato YAML. Non ho idea di cosa sia. – Elan

+0

Penso che i ":" nella descrizione legale aggiungano qualche difficoltà se si usa il 'yaml.load'. – cgage

+0

Provato 'read.delim' e ottenuto risultati davvero esilaranti ... lol – Elan

risposta

2

Ho scritto una funzione di analisi molto generica in grado di gestire qualsiasi modello di linea di separazione e separatore di valori di campo , specificato come regex parametrizzati. Rimuove facoltativamente anche gli spazi bianchi finali dai valori dei campi e passa argomenti variadici alla singola chiamata data.frame() che crea il data.frame risultante.

sectionedFieldLinesToFrame <- function(lines,divRE,sepRE,select,rtw=T,...) { 
    divLineIndexes <- grep(perl=T,divRE,lines); 
    ## remove possible leading and trailing divs, for robustness 
    if (length(divLineIndexes)>0L && divLineIndexes[1L]==1L) { 
     leadDivCount <- match(T,c(diff(divLineIndexes)!=1L,T)); 
     lines <- lines[-seq_len(leadDivCount)]; 
     divLineIndexes <- divLineIndexes[-seq_len(leadDivCount)]-leadDivCount; 
    }; ## end if 
    if (length(divLineIndexes)>0L && divLineIndexes[length(divLineIndexes)]==length(lines)) { 
     trailDivCount <- match(T,c(rev(diff(divLineIndexes)!=1L),T)); 
     lines <- lines[-seq(to=length(lines),len=trailDivCount)]; 
     divLineIndexes <- divLineIndexes[-seq(to=length(divLineIndexes),len=trailDivCount)]; 
    }; ## end if 
    ## get fields to extract 
    if (missing(select)) { 
     allFieldLineIndexes <- grep(perl=T,sepRE,lines); 
     fields <- unique(sub(perl=T,paste0(sepRE,'.*'),'',lines[allFieldLineIndexes])); 
    } else { 
     fields <- select; 
    }; ## end if 
    ## extract each field vector and build the data.frame 
    do.call(data.frame,c(setNames(lapply(fields,function(field) { 
     fieldLineIndexes <- grep(perl=T,paste0('^\\Q',field,'\\E',sepRE),lines); 
     sectionIndexes <- findInterval(fieldLineIndexes,divLineIndexes); ## 0-based 
     values <- sub(perl=T,paste0('^.*?',sepRE),'',lines[fieldLineIndexes]); 
     if (rtw) values <- sub(perl=T,'\\s+$','',values); 
     values[match(seq(0L,length(divLineIndexes)),sectionIndexes)]; 
    }),fields),...)); 
}; ## end sectionedFieldLinesToFrame() 

Ecco come usarlo:

fileName <- 'data.txt'; 
divRE <- '^-+/-+$'; 
sepRE <- ':\\s*'; 
df <- sectionedFieldLinesToFrame(readLines(fileName),divRE,sepRE,stringsAsFactors=F); 
str(df); 
## 'data.frame': 2 obs. of 8 variables: 
## $ Instrument  : chr "201301240005447" "201301240005408" 
## $ Recorded   : chr "01/24/2013" "01/24/2013" 
## $ Consideration : chr "$150,125.00" "$65,124.00" 
## $ Document.Type : chr "MORTGAGES" "MORTGAGES" 
## $ Pages   : chr "17" "17" 
## $ Grantor   : chr "BYRES, CONNIE R/BYRES, SCOTT" "SANNE, BETTY LOU/SANNE, KENNETH D" 
## $ Grantee   : chr "MORTGAGE ELECTRONIC REGISTRATION SYSTEMS INC/QUICKEN LOANS INC" "JPMORGAN CHASE BANK NA" 
## $ Legal.Description: chr "* St:5495 MCNAMARA LN City:FLINT PrpId:1135532002 CC:11 T:8 R:7 S:35 ext:PT OF NE4" "Sub:WOODCROFT NO 1 Lt:188 St:2213 RADCLIFFE AVE City:FLINT PrpId:4024106003 CC:54" 

è anche possibile specificare l'argomento select per selezionare esattamente quali campi si desidera estrarre:

select <- c('Instrument','Pages','Grantor'); 
df <- sectionedFieldLinesToFrame(readLines(fileName),divRE,sepRE,select,stringsAsFactors=F); 
df; 
##  Instrument Pages        Grantor 
## 1 201301240005447 17  BYRES, CONNIE R/BYRES, SCOTT 
## 2 201301240005408 17 SANNE, BETTY LOU/SANNE, KENNETH D 

Ho provato molto duramente a renderlo il più robusto possibile. Tratta con cura le possibili linee di divisione ridondanti di inizio e fine e gestisce correttamente il caso di campi incoerenti tra le sezioni.

Vale la pena sottolineare questo ultimo punto. Tutte le altre soluzioni offerte offrono presupposti estremamente fragili sui dati di input, o che ci sono esattamente 8 campi per sezione sempre nello stesso ordine, o che ogni nome di campo (eventualmente codificato) si verifica in ogni sezione. Se tale ipotesi viene violata, tali soluzioni diventano inutili. La mia funzione non formula alcuna ipotesi sul numero del campo, i nomi o la coerenza. Richiama in modo dinamico tutti i nomi dei campi presenti in qualsiasi sezione e crea i vettori corretti di ciascuno, generando gli elementi NA in cui il campo non è presente in una determinata sezione.

ecco alcuni esempi:

sectionedFieldLinesToFrame(character(),'^-$',':'); 
## data frame with 0 columns and 0 rows 
sectionedFieldLinesToFrame(rep('-',2L),'^-$',':'); 
## data frame with 0 columns and 0 rows 
sectionedFieldLinesToFrame(c('A:a','-'),'^-$',':'); 
## A 
## 1 a 
sectionedFieldLinesToFrame(c('A:a','-','-'),'^-$',':'); 
## A 
## 1 a 
sectionedFieldLinesToFrame(c('A:a','-','B:b','-'),'^-$',':'); 
##  A B 
## 1 a <NA> 
## 2 <NA> b 
sectionedFieldLinesToFrame(c('A:a','B:b','-','B:c','-'),'^-$',':'); 
##  A B 
## 1 a b 
## 2 <NA> c 
sectionedFieldLinesToFrame(c('A:a','B:b','-','B:c','-','A:d'),'^-$',':'); 
##  A B 
## 1 a b 
## 2 <NA> c 
## 3 d <NA> 
sectionedFieldLinesToFrame(c('-','-','A:a','B:b','-','B:c','-','A:d','C:e','-'),'^-$',':'); 
##  A B C 
## 1 a b <NA> 
## 2 <NA> c <NA> 
## 3 d <NA> e 
sectionedFieldLinesToFrame(c('-','A:a','B:b','-','-','B:c','-','A:d','C:e','-'),'^-$',':'); 
##  A B C 
## 1 a b <NA> 
## 2 <NA> <NA> <NA> 
## 3 <NA> c <NA> 
## 4 d <NA> e 
+0

Grazie, @bgoldst. Questo è davvero molto robusto e ha recuperato una grande quantità di dati utilizzabili. – Elan

7

Principiante con R quindi sono sicuro che le persone aggiungeranno modi migliori, ma eccone uno che funziona fintanto che i campi di ogni record sono fissati in numero e ordine;

# Use gsubfn to get read.pattern 
install.packages('gsubfn')  
library(gsubfn) 

# Read all data rows into 'data'  
data = read.pattern('Test/test.txt', '([^:]*):(.*)', as.is=TRUE, fill=TRUE) 

# Reshape the data to 8 columns  
df = as.data.frame(matrix(data$V2, ncol=8, byrow=TRUE)) 

# Set the column names to reasonable values. 
colnames(df) = data$V1[1:8] 

     Instrument Recorded Consideration Document Type Pages        Grantor               Grantee                 Legal Description 
1 201301240005447 01/24/2013 $150,125.00 MORTGAGES  17  BYRES, CONNIE R/BYRES, SCOTT MORTGAGE ELECTRONIC REGISTRATION SYSTEMS INC/QUICKEN LOANS INC * St:5495 MCNAMARA LN City:FLINT PrpId:1135532002 CC:11 T:8 R:7 S:35 ext:PT OF NE4 
2 201301240005408 01/24/2013 $65,124.00 MORTGAGES  17 SANNE, BETTY LOU/SANNE, KENNETH D           JPMORGAN CHASE BANK NA Sub:WOODCROFT NO 1 Lt:188 St:2213 RADCLIFFE AVE City:FLINT PrpId:4024106003 CC:54 
+0

Sono nuovo alle espressioni regolari e sto avendo difficoltà ad afferrare cosa sta succedendo esattamente con tutti i simboli, ad es. ''([^:] *): (. *)''. Puoi analizzare esattamente cosa sta succedendo qui? – cgage

+1

@cgage La regex ha 2 "gruppi di cattura" (racchiusi tra parentesi) che si trasformano in due colonne, V1 e V2. La prima cattura '([^:] *)' corrisponde a tutti i caratteri che non sono _non_ i due punti. '[^:]' corrisponde a qualsiasi carattere che non è due punti e '*' significa zero o più di quelli. Questo si trasformerà nel nome del campo poiché tutto fino ai primi due punti è il nome. I due punti tra i gruppi corrispondono ai due punti che vogliamo rimuovere. Il secondo gruppo di cattura '(. *)' Corrisponde a tutto il resto alla fine della riga ('.' è qualsiasi carattere e' * 'significa zero o più) –

+1

@cgage [Regex101] (https://regex101.com/ r/dX7jS4/1) fornisce anche spiegazioni decenti con maggiori dettagli. –

2
rl <- readLines(textConnection('Instrument: 201301240005447 
Recorded: 01/24/2013 
Consideration: $150,125.00 
Document Type: MORTGAGES 
Pages: 17 
Grantor: BYRES, CONNIE R/BYRES, SCOTT 
Grantee: MORTGAGE ELECTRONIC REGISTRATION SYSTEMS INC/QUICKEN LOANS INC 
Legal Description: * St:5495 MCNAMARA LN City:FLINT PrpId:1135532002 CC:11 T:8 R:7 S:35 ext:PT OF NE4 
* 
    ---------------------------------/--------------------------------- 
Instrument: 201301240005408 
Recorded: 01/24/2013 
Consideration: $65,124.00 
Document Type: MORTGAGES 
Pages: 17 
Grantor: SANNE, BETTY LOU/SANNE, KENNETH D 
Grantee: JPMORGAN CHASE BANK NA 
Legal Description: Sub:WOODCROFT NO 1 Lt:188 St:2213 RADCLIFFE AVE City:FLINT PrpId:4024106003 CC:54 
* 
    ---------------------------------/---------------------------------')) 

È possibile utilizzare questo per selezionare cose che si desidera, definire una funzione di supporto per estrarre ogni campo (simile a una domanda ho risposto earlier today)

n <- c('Instrument', 'Recorded', 'Consideration', 'Document Type', 
     'Pages', 'Grantor', 'Grantee', 'Legal Description') 
f <- function(what, string = rl) { 
    gsub(sprintf('%s\\:\\s*([^~]*)|.', what), '\\1', string, perl = TRUE) 
} 

## read in the lines and do some minimal processing 
rl <- gsub('^\\* ', '\n', rl[grepl('^[A-Z*]', rl)]) 
rl <- paste0(rl, collapse = '~') 
rl <- strsplit(rl, '\\n')[[1]] 

data.frame(setNames(lapply(n, f), n)) 

#  Instrument Recorded Consideration Document.Type Pages 
# 1 201301240005447 01/24/2013 $150,125.00 MORTGAGES  17 
# 2 201301240005408 01/24/2013 $65,124.00 MORTGAGES  17 
#        Grantor 
# 1  BYRES, CONNIE R/BYRES, SCOTT 
# 2 SANNE, BETTY LOU/SANNE, KENNETH D 
#               Grantee 
# 1 MORTGAGE ELECTRONIC REGISTRATION SYSTEMS INC/QUICKEN LOANS INC 
# 2           JPMORGAN CHASE BANK NA 
#                 Legal.Description 
# 1 * St:5495 MCNAMARA LN City:FLINT PrpId:1135532002 CC:11 T:8 R:7 S:35 ext:PT OF NE4 
# 2 Sub:WOODCROFT NO 1 Lt:188 St:2213 RADCLIFFE AVE City:FLINT PrpId:4024106003 CC:54 

o

n <- c('Instrument', 'Recorded', 'Consideration') 
data.frame(setNames(lapply(n, f), n)) 

#  Instrument Recorded Consideration 
# 1 201301240005447 01/24/2013 $150,125.00 
# 2 201301240005408 01/24/2013 $65,124.00 
1

soluzione utilizzando solo {} di base, senza espressioni regolari.Questo non è molto elegante:

# read file and parse out values from field names 
q <- readLines('ugly.txt') 
q <- lapply(X = q, FUN = strsplit, split = ': ') 
q <- unlist(q) 
q <- matrix(data = q, ncol = 2, byrow = T) 
COLUMNS <- unique(q[,1]) 
q <- q[,2] 

# move values to rows of a DF and set names for DF 
q <- matrix(data = q, ncol = 9, byrow = T) 
q <- data.frame(q) 
names(q) <- COLUMNS 

# clean up data types 
q$Recorded <- as.Date(q$Recorded) 
q$Consideration <- as.numeric(q$Consideration) 
q$Pages <- as.numeric(q$Pages) 

View(q) 
Problemi correlati