【问题标题】:Factors in RSQLiteRSQLite 中的因素
【发布时间】:2013-11-04 11:15:55
【问题描述】:

我一直无法找到有关 RSQLite 如何处理因子的文档。通过快速测试(见下文),看起来它们已转换为字符。

问题 1:有没有办法将它们保存为因子?我可以想到一些笨拙的方法(主要涉及一个单独的表或存储因子级别的.Rdata 文件),但似乎应该有一个标准的因此更易于维护的方法。

问题2:如果不是RSQLite,比其他一些数据库或类似数据库的包?我在这里的用例很简单:在处理每个大的(2-5mm 行 X 550 列)data.frames 以构建一个巨大的数据库时附加一堆,然后只能从该数据库中选择我想要的行带来进入data.table 并继续工作。

library(RSQLite)
# Create
db <- dbConnect( SQLite(), dbname="~/temp/test.sqlite" )
# Write test
set.seed(1)
testDat <- data.frame(x=runif(1000),y=runif(1000),g1=sample(letters[1:10],1000,replace=TRUE),g2=rep(letters[1:10],each=100),g3=factor( sample(letters[1:10],1000,replace=TRUE) ))
if(dbExistsTable(db,"test")) dbRemoveTable(db,"test")
dbWriteTable( conn = db, name = "test", value = testDat, row.names=FALSE )
# Read test
testRecovery <- dbGetQuery(db, "SELECT * FROM test")
testSelection <- dbGetQuery(db, "SELECT * FROM test WHERE g3=='h' OR g3=='e' ")
# Close
dbDisconnect(db)

【问题讨论】:

  • 旁注:我想你知道character is often preferred to factor in data table fo key。什么是因子的 dbType 等价物?整数?我的意思是你应该在读取表格时存储列类型信息以保留因子。
  • 据我所知,目前没有办法在 RSQLite 或任何基于 DBI 的包中执行此操作。挑战在于如何存储有关因素的元数据。此外,将它们存储为字符的缺点也不是很大。
  • @hadley 我担心的是,如果我在其上附加一个在字符串中具有额外(按字母顺序排列)级别的 data.frame,则参考类别可能会发生变化。至少有会返回错误的因素。
  • @AriB.Friedman 如果您的模型对参考水平敏感,那么有问题。
  • @hadley 同意。我应该更清楚。假设我在参考水平为黑色的一台机器上运行回归并保存模型。然后,我添加了一些数据,这些数据会导致新的参考电平发生切换。预测函数期望模型矩阵中有colorRed、colorBlue等。但是现在有一个 colorBlack,因为我在数据库中添加了“茄子”,当它被转换回一个因子时,它成为了第一级。我不在乎茄子在那里,因为我正在子集化以避免那些年的数据。这无疑是偏执狂,但是....

标签: r sqlite rsqlite


【解决方案1】:

对我来说看起来很简单:factor 是一个只有 S 和 R 知道的概念。句号。

因此,要将它们放入数据库并返回,您需要编写映射器。要么简单化,要么做所有事情as.character(并假设大多数数据库后端会像 R 一样散列字符串)。或者以 DB 为中心并将因子拆分为(无符号)(可能是短的)整数和标签。

【讨论】:

  • 好的。我想我会写一个包装器;-)。
【解决方案2】:

好的,我按照@DirkEddelbuettel 的建议写了一些包装器。欢迎评论。

#' Write a table via RSQLite with factors stored in another table
#' Handles data.tables efficiently for large datasets
#' @param conn The connection object (created with e.g. dbConnect)
#' @param name The name of the table to write
#' @param value The data.frame to write to the database
#' @param factorName The base name of the tables to store the factor labels in in the SQLite database (e.g. if factorName is "_factor_" and the data.frame in value contains a factor column called "color" and the name is "mytable" then dbWriteFactorTable will create a table called mytable_factor_color which will store the levels information)
#' @param \dots Options to pass along to dbWriteTable (e.g. append=TRUE)
#' @return A boolean indicating whether the table write was successful
dbWriteFactorTable <- function( conn, name, value, factorName="_factor_", ... ) {
  # Test inputs
  stopifnot(class(conn)=="SQLiteConnection")
  stopifnot(class(name)=="character")
  stopifnot("data.frame" %in% class(value))
  stopifnot(class(factorName)=="character")
  if( grepl("[.]",factorName) ) stop("factorName must use valid characters for SQLite")
  if( "data.table" %in% class(value) ) dt <- TRUE # Is value a data.table, if so use more efficient methods
  # Convert factors to character
  factorCols <- names( Filter( function(x) x=="factor", vapply( value, class, "" ) ) )
  if(length(factorCols>0)) {
    for( cl in which( colnames(value) %in% factorCols ) ) {
      cn <- colnames(value)[cl]
      factorTable <- data.frame( levels=levels(value[[ cn ]]) )
      factorTable$levelKey <- seq(nrow(factorTable))
      fctNm <- paste0(name,factorName,cn)
      dbWriteTable( conn = conn, name = fctNm, value = factorTable, row.names=FALSE, overwrite=TRUE )
      if( dt )  set( x=value, j=cl, value=as.character(value[[ cn ]]) )
    }
    if( !dt )  value <- japply( value, which( colnames(value) %in% factorCols ), as.character )
  } else {
    warning("No factor columns detected.")
  }
  dbWriteTable( conn = conn, name = name, value = value, ... )
}

#' Read a table via RSQLite with factors stored in another table
#' @param conn The connection object (created with e.g. dbConnect)
#' @param name The name of the table to read
#' @param query A character string containing sequel statements to be appended onto the query (e.g. "WHERE x==3")
#' @param dt Whether to return a data.table vs. a plain-old data.frame
#' @param factorName The base name of the tables to store the factor labels in in the SQLite database (e.g. if factorName is "_factor_" and the data.frame in value contains a factor column called "color" and the name is "mytable" then dbWriteFactorTable will expect there to be a table called mytable_factor_color which holds the levels information)
#' @param \dots Options to pass along to dbGetQuery
#' @return A data.table or data.frame
dbReadFactorTable <- function( conn, name, query="", dt=TRUE, factorName="_factor_", ... ) {
  # Test inputs
  stopifnot(class(conn)=="SQLiteConnection")
  stopifnot(class(name)=="character")
  stopifnot(class(factorName)=="character")
  if( grepl("[.]",factorName) ) stop("factorName must use valid characters for SQLite")
  # Read main table
  if( dt ) {
    value <- as.data.table( dbGetQuery( conn, paste("SELECT * FROM",name,query), ... ) )
  } else {
    value <- dbGetQuery( conn, paste("SELECT * FROM",name,query), ... )
  }
  # Convert factors to character
  factorCols <- sub( paste0("^.*",name,factorName,"(.+)$"), "\\1", 
    Filter( Negate(is.na), 
      str_extract( dbListTables( conn ), paste0(".*",name,factorName,".*") ) 
    )
  )
  if(length(factorCols>0)) {
    for( cn in factorCols ) {
      fctNm <- paste0(name,factorName,cn)
      factorTable <- dbGetQuery( conn, paste0("SELECT * FROM ",fctNm) )
      if( dt ) {
        cl <- which( colnames(value) %in% cn )
        set( x=value, j=cl, value=factor( value[[ cn ]], levels=factorTable$levels ) )
      } else {
        value[[ cn ]] <- factor( value[[ cn ]], levels=factorTable$levels )
      }
    }
  } else {
    warning("No factor columns detected.")
  }
  value
}

还有一个简单的例子:

db <- dbConnect( SQLite(), dbname="~/temp/test.sqlite" )
set.seed(1)
n <- 1000
testDat <- data.frame(key=seq(n), x=runif(n),y=runif(n),g1=sample(letters[1:10],n,replace=TRUE),g2=rep(letters[1:10],each=n/10),g3=factor( sample(letters[1:10],n,replace=TRUE) ))
if(dbExistsTable(db,"test")) dbRemoveTable(db,"test")
dbWriteFactorTable( conn = db, name = "test", value = as.data.table(testDat), row.names=FALSE )
dbReadFactorTable( conn = db, name = "test" )
dbReadFactorTable( conn = db, name = "test", query="WHERE g3=='a'" )

【讨论】:

    猜你喜欢
    • 2014-04-21
    • 2012-11-07
    • 1970-01-01
    • 2020-06-24
    • 1970-01-01
    • 1970-01-01
    • 2012-10-01
    • 1970-01-01
    • 2011-11-27
    相关资源
    最近更新 更多