leer / escribir datos en formato libsvm

¿Cómo leo / escribo datos libsvm en / desde R ?

El formato libsvm es de datos dispersos como

 [ :]* 

(véase almacenamiento de filas comprimidas (CRS) ), por ejemplo,

 1 10:3.4 123:0.5 34567:0.231 0.2 22:1 456:03 

Estoy seguro de que puedo azotar algo yo mismo, pero preferiría usar algo de la estantería. Sin embargo, R library foreign no parece proporcionar la funcionalidad necesaria.

e1071 está fuera de la plataforma:

 install.packages("e1071") library(e1071) read.matrix.csr(...) write.matrix.csr(...) 

Nota : está implementado en R , no en C , por lo que es lento como un perro .

Incluso tiene una viñeta especial Support Vector Machines: la interfaz para libsvm en el paquete e1071 .

r.vw se incluye con vowpal_wabbit

Nota : está implementado en R , no en C , por lo que es lento como un perro .

He estado ejecutando un trabajo usando la solución zygmuntz en un conjunto de datos con 25k observaciones (filas) durante casi 5 horas. Ha hecho filas de 3k-ish. Me tomó tanto tiempo codificar esto mientras tanto (basado en el código de zygmuntz):

 require(Matrix) read.libsvm = function( filename ) { content = readLines( filename ) num_lines = length( content ) tomakemat = cbind(1:num_lines, -1, substr(content,1,1)) # loop over lines makemat = rbind(tomakemat, do.call(rbind, lapply(1:num_lines, function(i){ # split by spaces, remove lines line = as.vector( strsplit( content[i], ' ' )[[1]]) cbind(i, t(simplify2array(strsplit(line[-1], ':')))) }))) class(makemat) = "numeric" #browser() yx = sparseMatrix(i = makemat[,1], j = makemat[,2]+2, x = makemat[,3]) return( yx ) } 

Esto se ejecutó en minutos en la misma máquina (también puede haber problemas de memoria con la solución zygmuntz, no estoy seguro). Espero que esto ayude a cualquiera con el mismo problema.

Recuerde, si necesita hacer grandes cálculos en R, VECTORIZE!

EDITAR: solucionó un error de indexación que encontré esta mañana.

Basado en algunos comentarios Lo agrego como un aswer para que sea más fácil de usar para otros. Esto es para escribir datos en formato libsvm.

Función para escribir un data.frame al formato svm light. Agregué un argumento train = {TRUE, FALSE} en caso de que los datos no tengan tags. En este caso, el índice de clase se ignora.

 write.libsvm = function(data, filename= "out.dat", class = 1, train=TRUE) { out = file(filename) if(train){ writeLines(apply(data, 1, function(X) { paste(X[class], apply(cbind(which(X!=0)[-class], X[which(X!=0)[-class]]), 1, paste, collapse=":"), collapse=" ") }), out) } else { # leaves 1 as default for the new data without predictions. writeLines(apply(data, 1, function(X) { paste('1', apply(cbind(which(X!=0), X[which(X!=0)]), 1, paste, collapse=":"), collapse=" ") }), out) } close(out) } 

** EDIT **

Otra opción: en caso de que ya tenga los datos en un objeto data.table

libfm y SVMlight tienen el mismo formato, por lo que esta función debería funcionar.

 library(data.table) data.table.fm <- function (data = X, fileName = "../out.fm", target = "y_train", train = TRUE) { if (train) { if (is.logical(data[[target]]) | sum(levels(factor(data[[target]])) == levels(factor(c(0, 1)))) == 2) { data[[target]][data[[target]] == TRUE] = 1 data[[target]][data[[target]] == FALSE] = -1 } } specChar = "\\(|\\)|\\||\\:" specCharSpace = "\\(|\\)|\\||\\:| " parsingNames <- function(x) { ret = c() for (el in x) ret = append(ret, gsub(specCharSpace, "_", el)) ret } parsingVar <- function(x, keepSpace, hard_parse) { if (!keepSpace) spch = specCharSpace else spch = specChar if (hard_parse) gsub("(^_( *|_*)+)|(^_$)|(( *|_*)+_$)|( +_+ +)", " ", gsub(specChar, "_", gsub("(^ +)|( +$)", "", x))) else gsub(spch, "_", x) } setnames(data, names(data), parsingNames(names(data))) target = parsingNames(target) format_vw <- function(column, formater) { ifelse(as.logical(column), sprintf(formater, j, column), "") } all_vars = names(data)[!names(data) %in% target] cat("Reordering data.table if class isn't first\n") target_inx = which(names(data) %in% target) rest_inx = which(!names(data) %in% target) cat("Adding Variable names to data.table\n") for (j in rest_inx) { column = data[[j]] formater = "%s:%f" set(data, i = NULL, j = j, value = format_vw(column, formater)) cat(sprintf("Fixing %s\n", j)) } data = data[, c(target_inx, rest_inx), with = FALSE] drop_extra_space <- function(x) { gsub(" {1,}", " ", x) } cat("Pasting data - Removing extra spaces\n") data = apply(data, 1, function(x) drop_extra_space(paste(x, collapse = " "))) cat("Writing to disk\n") write.table(data, file = fileName, sep = " ", row.names = FALSE, col.names = FALSE, quote = FALSE) } 

data.table mi propia solución ad hoc aprovechando algunas utilidades de data.table

Se ejecutó en muy poco tiempo en el conjunto de datos de prueba que encontré ( datos de la vivienda de Boston ).

Convirtiendo eso en un data.table (ortogonal a la solución, pero agregando aquí para una fácil reproducibilidad):

 library(data.table) x = fread("/media/data_drive/housing.data.fw", sep = "\n", header = FALSE) #usually fixed-width conversion is harder, but everything here is numeric columns = c("CRIM", "ZN", "INDUS", "CHAS", "NOX", "RM", "AGE", "DIS", "RAD", "TAX", "PTRATIO", "B", "LSTAT", "MEDV") DT = with(x, fread(paste(gsub("\\s+", "\t", V1), collapse = "\n"), header = FALSE, sep = "\t", col.names = columns)) 

Aquí está:

 DT[ , fwrite(as.data.table(paste0( MEDV, " | ", sapply(transpose(lapply( names(.SD), function(jj) paste0(jj, ":", get(jj)))), paste, collapse = " "))), "/path/to/output", col.names = FALSE, quote = FALSE), .SDcols = !"MEDV"] #what gets sent to as.data.table: #[1] "24 | CRIM:0.00632 ZN:18 INDUS:2.31 CHAS:0 NOX:0.538 RM:6.575 # AGE:65.2 DIS:4.09 RAD:1 TAX:296 PTRATIO:15.3 B:396.9 LSTAT:4.98 MEDV:24" #[2] "21.6 | CRIM:0.02731 ZN:0 INDUS:7.07 CHAS:0 NOX:0.469 RM:6.421 # AGE:78.9 DIS:4.9671 RAD:2 TAX:242 PTRATIO:17.8 B:396.9 LSTAT:9.14 MEDV:21.6" # ... 

Puede haber una manera mejor de entender esto mediante fwrite que como as.data.table , pero no puedo pensar en uno ( hasta que setDT funcione en vectores ).

Lo repliqué para probar su rendimiento en un conjunto de datos más grande (simplemente explotar el conjunto de datos actual):

 DT2 = rbindlist(replicate(1000, DT, simplify = FALSE)) 

La operación fue bastante rápida en comparación con algunas de las veces reportadas aquí (no me molesté en comparar directamente todavía):

 system.time(.) # user system elapsed # 8.392 0.000 8.385 

También probé utilizando writeLines lugar de fwrite , pero este último era mejor.


Estoy buscando de nuevo y ver que puede llevar un tiempo averiguar qué está pasando. Tal vez la versión de magrittr -piped será más fácil de seguir:

 DT[ , #1) prepend each column's values with the column name lapply(names(.SD), function(jj) paste0(jj, ":", get(jj))) %>% #2) transpose this list (using data.table's fast tool) # (was column-wise, now row-wise) #3) concatenate columns, separated by " " transpose %>% sapply(paste, collapse = " ") %>% #4) prepend each row with the target value # (with Vowpal Wabbit in mind, separate with a pipe) paste0(MEDV, " | ", .) %>% #5) convert this to a data.table to use fwrite as.data.table %>% #6) fwrite it; exclude nonsense column name, # and force quotes off fwrite("/path/to/data", col.names = FALSE, quote = FALSE), .SDcols = !"MEDV"] 

leer en tales archivos es mucho más fácil **

 #quickly read data; don't split within lines x = fread("/path/to/data", sep = "\n", header = FALSE) #tstrsplit is transpose(strsplit(.)) dt1 = x[ , tstrsplit(V1, split = "[| :]+")] #even columns have variable names nms = c("target_name", unlist(dt1[1L, seq(2L, ncol(dt1), by = 2L), with = FALSE])) #odd columns have values DT = dt1[ , seq(1L, ncol(dt1), by = 2L), with = FALSE] #add meaningful names setnames(DT, nms) 

** esto no funcionará con datos de entrada “raídos” / dispersos. No creo que haya una manera de extender esto para trabajar en tales casos.

Pruebe estas funciones y ejemplos:

https://github.com/zygmuntz/r-libsvm-format-read-write

Fui con una solución de dos saltos: convertir datos R a otro formato primero, y luego a LIBSVM:

  1. Se utiliza el paquete R extraño para convertir (y escribir) el dataframe al formato ARFF (write.arff modificado cambiando write.table a na = “0.0” en lugar de na = “?” De lo contrario, el paso 2 falla)
  2. Utiliza https://github.com/dat/svm-tools/blob/master/arff2svm.py para convertir el formato ARFF a LIBSVM

Mi conjunto de datos es de 200K x 500 y esto solo tomó 3-5 minutos.

    Intereting Posts