sqldf: запросить данные по диапазону дат

Я читаю из огромного текстового файла с форматом даты '%d/%m/%Y'. Я хочу использовать read.csv.sql из sqldf для одновременного чтения и фильтрации данных по дате. Это сделано для экономии использования памяти и времени выполнения, пропуская многие даты, которые меня не интересуют. Я знаю, как это сделать с помощью dplyr и lubridate, но я просто хочу попробовать с sqldf по вышеупомянутой причине. Несмотря на то, что я хорошо знаком с синтаксисом SQL, он все равно меня понимает большую часть времени, без исключения sqldf.

Выполненная команда, подобная следующей, вернула data.frame с 0 строкой:

first_date <- "2001-11-1"
second_date <- "2003-11-1"
query <- "select * from file WHERE strftime('%d/%m/%Y', Date, 'unixepoch', 'localtime') between
                    '$first_date' AND '$second_date'"
df <- read.csv.sql(data_file,
                       sql= query,
                       stringsAsFactors=FALSE,
                       sep = ";", header = TRUE)

Итак, для моделирования я попробовал использовать функцию sqldf, например:

first_date <- "2001-11-1"
second_date <- "2003-11-1"
df2 <- data.frame( Date = paste(rep(1:3, each = 4), 11:12, 2001:2012, sep = "/"))
sqldf("SELECT * FROM df2 WHERE strftime('%d/%m/%Y', Date, 'unixepoch') BETWEEN '$first-date' AND '$second_date' ") 

# Expect:
# Date
# 1  1-11-2001
# 2  1-12-2002
# 3  1-11-2003

person biocyberman    schedule 07.09.2014    source источник
comment
Вы уверены, что формат файла не дд-мм-гггг? Обычно, если день или месяц равны 1, то он выражается как 01, а не 1.   -  person G. Grothendieck    schedule 08.09.2014
comment
Данные в текстовом файле действительно имеют такие даты 1/1/2011, а не 01/01/2011. И в любом случае, я думаю, strftime функция драйвера SQLite сможет преобразовать в более поздний формат. Моя трудность в том, что я не знаю, как проверить функцию strftime в этом случае.   -  person biocyberman    schedule 08.09.2014
comment
SQLite не имеет типа даты. Вам может пригодиться следующее: stackoverflow.com/questions/4428795 /   -  person nicola    schedule 08.09.2014
comment
@nicola Это правда. Вот почему я использую функцию strftime для преобразования строк времени в даты на лету и сравнения этих дат. Я просто не знаком с функцией правильного вызова.   -  person biocyberman    schedule 08.09.2014


Ответы (1)


strftime strftime с кодами процентов используется для преобразования объекта, уже рассматриваемого sqlite как datetime, во что-то еще, но вам нужно обратное, поэтому подход в вопросе не будет работать. Например, здесь мы конвертируем текущее время в строку дд-мм-гггг:

library(sqldf)
sqldf("select strftime('%d-%m-%Y', 'now') now")
##          now
## 1 07-09-2014

Обсуждение. Поскольку в SQlite отсутствуют типы даты, справиться с этим немного обременительно, особенно с нестандартными форматами даты, состоящими из 1 или 2 цифр, но если вы действительно хотите использовать SQLite, мы можем сделать это утомительно разбирая строки даты. Использование fn$ из пакета gsubfn для интерполяции строк немного упрощает это.

Код ниже zero2d выводит код SQL для добавления символа нуля к его входным данным, если это одна цифра. rmSlash выводит код SQL для удаления любых косых черт в своем аргументе. Year, Month и Day каждый выходной код SQL принимает символьную строку, представляющую дату в обсуждаемом формате, и извлекает указанный компонент, переформатируя его как двухзначную строку символов, заполненную нулями, в случае Month и Day. fmtDate принимает символьную строку формы, показанной в вопросе для first_string и second_string, и выводит yyyy-mm-dd символьную строку.

library(sqldf)
library(gsubfn)

zero2d <- function(x) sprintf("substr('0' || %s, -2)", x)

rmSlash <- function(x) sprintf("replace(%s, '/', '')", x)

Year <- function(x) sprintf("substr(%s, -4)", x)

Month <- function(x) {
   y <- sprintf("substr(%s, instr(%s, '/') + 1, 2)", x, x)
   zero2d(rmSlash(y))
}

Day <- function(x) {
   y <- sprintf("substr(%s, 1, 2)", x)
   zero2d(rmSlash(y))
}

fmtDate <- function(x) format(as.Date(x))

sql <- "select * from df2 where 
  `Year('Date')` || '-' || 
  `Month('Date')` || '-' || 
  `Day('Date')`
  between '`fmtDate(first_date)`' and '`fmtDate(second_date)`'"
fn$sqldf(sql)

давая:

       Date
1 1/11/2001
2 1/12/2002
3 1/11/2003

Примечания

1) Используемые функции SQLite instr, replace и substr являются основными функциями sqlite

2) SQL. Фактический оператор SQL, который выполняется после fn$ выполнения подстановок, выглядит следующим образом (слегка переформатирован, чтобы соответствовать):

> cat( fn$identity(sql), "\n")
select * from df2 where 
  substr(Date, -4) 
  || '-' || 
  substr('0' || replace(substr(Date, instr(Date, '/') + 1, 2), '/', ''), -2) 
  || '-' || 
  substr('0' || replace(substr(Date, 1, 2), '/', ''), -2)
  between '2001-11-01' and '2003-11-01' 

3) источник осложнений. Основное затруднение - нестандартные 1-2 цифры дня и месяца. Если бы они были последовательно двухзначными, это сократилось бы до следующего:

first_date <- "2001-11-01"
second_date <- ""2003-11-01"

fn$sqldf("select Date from df2 
   where substr(Date, -4) || '-' || 
         substr(Date, 4, 2) || '-' ||
         substr(Date, 1, 2)
   between '`first_date`' and '`second_date`' ")

4) H2 Вот раствор H2. H2 действительно имеет тип datetime, существенно упрощающий решение по сравнению с SQLite. Мы предполагаем, что данные находятся в файле с именем mydata.dat. Обратите внимание, что read.csv.sql не поддерживает H2, поскольку H2 уже имеет для этого внутреннюю функцию csvread SQL:

library(RH2)
library(sqldf)

first_date <- "2001-11-01"
second_date <- "2003-11-01"

fn$sqldf(c("CREATE TABLE t(DATE TIMESTAMP) AS
  SELECT parsedatetime(DATE, 'd/M/y') as DATE
  FROM CSVREAD('mydata.dat')", 
  "SELECT DATE FROM t WHERE DATE between '`first_date`' and '`second_date`'"))

Обратите внимание, что первый запрос RH2 будет медленным в сеансе, поскольку он загружает java. После этого вы можете попробовать его, чтобы убедиться, что производительность адекватна.

person G. Grothendieck    schedule 07.09.2014
comment
Это довольно трудоемкое решение. Большое спасибо за работу для меня. - person biocyberman; 08.09.2014
comment
Добавили примечание в конце, указывающее, что сложность в основном связана с 1 или 2 цифрами дня и месяца, и если бы они были последовательно 2-значными, решение существенно сократилось. - person G. Grothendieck; 08.09.2014
comment
Вы абсолютно правы. Я думал об использовании Rcpp или чего-то еще, используя внешнюю программу, чтобы вроде sed исправить даты, прежде чем использовать R для работы с данными. Но поскольку я новичок в R, я хочу узнать, как это работает в R. Кстати, я только что узнал, что вы являетесь одним из авторов пакета RH2. Мой респект и приятный сюрприз :-). Не могли бы вы продемонстрировать, как можно работать с RH2 вместо SQLite? Еще раз спасибо. - person biocyberman; 08.09.2014
comment
Из того, что здесь написано: h2database.com/html/datatypes.html#date_type. Я думаю, что мы не можем избежать этапов извлечения year, month и day и переформатирования дат. Так что код не выглядел бы короче. Это правда? - person biocyberman; 08.09.2014
comment
В конце добавили раствор H2. - person G. Grothendieck; 08.09.2014
comment
Это выглядит определенно чище. Я попытался использовать реальные данные и получил ошибку типа: execute JDBC update query failed in dbSendUpdate (Out of memory. Я запустил код в RStudio и хотел выбрать все столбцы. Это, вероятно, превышает предел RStudio. Итак, я пока остановлюсь на решении на основе SQLite. - person biocyberman; 09.09.2014
comment
Попробуйте это в R: options(java.parameters="-Xmx1024m") перед загрузкой любых пакетов или используйте другой размер памяти. - person G. Grothendieck; 09.09.2014