Любой способ преобразовать обычные текстовые данные в обычные табличные данные с помощью утилит dplyr? - PullRequest
0 голосов
/ 04 мая 2018

У меня есть данные в сетке в формате простого текста ASCII (, пожалуйста, посмотрите исходные необработанные данные на лету ), и все наблюдения данных проводились на ежедневном уровне, где годовые данные были собраны в сетке. Однако я хочу восстановить эти данные, потому что я хочу делать ежегодную статистику. Для этого мне нужно восстановить эти простые текстовые данные в виде табличных данных в виде матриц, где ежедневное наблюдение за данными будет в новых столбцах, так что было бы легче сделать среднее значение за год.

обновление

Поскольку исходные необработанные текстовые данные (( посмотрите на исходные необработанные данные на лету )) довольно большие, здесь я просто приведу общий обзор исходных данных.

обновление 2 :

Я импортировал исходные неформатированные данные в ASCII в R, вот скрипт R ниже:

rawdata = read.table(file = "~/25_krig_all_1980", header = FALSE, fill = TRUE, comment.char="Y", stringsAsFactors=FALSE )
colnames(rawdata) = c("long", "lat", "precip", "err1", "err2")

Вот как выглядит скелет исходных необработанных текстовых данных (, посмотрите на урезанные необработанные данные на лету ) в notepad++:

1980   1   1   1
      6.125 47.375     0.0    20.00     1.0
      6.375 47.375     0.0    19.99     1.0
      6.625 47.375     0.0    19.97     1.0
      6.875 47.375     0.0    19.84     1.0
      7.125 47.375     0.0    20.00     1.0
 1980   1   2   2
      6.125 47.375     1.5    20.00     1.0
      6.375 47.375     1.5    19.99     1.0
      6.625 47.375     1.5    19.97     1.0
      6.875 47.375     1.5    19.84     1.0
      7.125 47.375     2.9    20.00     1.0
 1980   1   3   3
      6.125 47.375     3.3    20.00     1.0
      6.375 47.375     3.3    19.99     1.0
      6.625 47.375     3.3    19.97     1.0
      6.875 47.375     3.3    19.84     1.0
      7.125 47.375     1.3    20.00     1.0
 1980   1   4   4
      6.125 47.375     3.8    20.00     1.0
      6.375 47.375     3.8    19.99     1.0
      6.625 47.375     3.8    19.97     1.0
      6.875 47.375     3.7    19.84     1.0
      7.125 47.375     3.7    20.00     1.0
 1980   1   5   5
      6.125 47.375     2.2    20.00     1.0
      6.375 47.375     2.2    19.99     1.0
      6.625 47.375     2.2    19.97     1.0
      6.875 47.375     2.2    19.84     1.0
      7.125 47.375     4.8    20.00     1.0

Вот мой минимальный пример для объяснения исходных необработанных текстовых данных:

foo = read.table("grid_data_demo.txt", header=FALSE, skip=1, nrows = 5)
colnames(foo) = c("long", "lat", "precip", "err1", "err2")

обновление 3 :

в исходных текстовых данных нет разделителя текста, нет списка, в котором все данные помещены в чистый текст. Я создал miniDat в качестве воспроизводимого примера, потому что я хочу получить список, подобный объекту, из исходных необработанных данных ((, пожалуйста, посмотрите пример фрагмента данных на лету ))

miniDat = list(dat_19800101 = data.frame(long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
                                         lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
                                         precip=c(0, 0, 0, 0, 0),
                                         err1=c(20.00, 19.99, 19.97, 19.84, 20.00),
                                         err2=c(1, 1, 1, 1, 1)),
               dat_19800102 = data.frame(long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
                                         lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
                                         precip=c(1.5, 1.5, 1.5, 1.5, 2.9),
                                         err1=c(20.00, 19.99, 19.97, 19.84, 20.00),
                                         err2=c(1, 1, 1, 1, 1)),

               dat_19800103 = data.frame(long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
                                         lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
                                         precip=c(3.3, 3.3, 3.3, 3.3, 1.3),
                                         err1=c(20.00, 19.99, 19.97, 19.84, 20.00),
                                         err2=c(1, 1, 1, 1, 1)),
               dat_19800104 = data.frame(long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
                                         lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
                                         precip=c(3.8, 3.8, 3.8, 3.7, 3.7),
                                         err1=c(20.00, 19.99, 19.97, 19.84, 20.00),
                                         err2=c(1, 1, 1, 1, 1)),
               dat_19800105 = data.frame(long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
                                         lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
                                         precip=c(2.2, 2.2, 2.2, 2.2, 4.8),
                                         err1=c(20.00, 19.99, 19.97, 19.84, 20.00),
                                         err2=c(1, 1, 1, 1, 1)))

Итак, я хочу восстановить табличные данные в виде матриц по исходным необработанным текстовым данным и сделать ежегодную статистику для каждой точки сетки соответственно. Возможно, dplyr или data.table предоставляет утилиты для обработки такого рода манипуляций. Есть ли быстрое решение для этого преобразования данных? Как я могу сделать это легко в dplyr утилитах? Есть идеи?

желаемый вывод :

В ожидаемом выводе

я бы хотел отбросить столбцы fourth (err1) и fifth (err2), сохраняя при этом то же измерение столбцов long и lat с соответствующим суточным значением precip, что и новый столбец. Вот воспроизводимый пример моего ожидаемого результата:

desired_output = data.frame(
    long=c( 6.125 ,6.375, 6.625, 6.875, 7.125),
    lat=c(47.375, 47.375, 47.375, 47.375, 47.375),
    precip_day1=c(0, 0, 0, 0, 0),
    precip_day2=c(1.5, 1.5, 1.5, 1.5, 2.9),
    precip_day3=c(3.3, 3.3, 3.3, 3.3, 1.3),
    precip_day4=c(3.8, 3.8, 3.8, 3.7, 3.7),
    precip_day5=c(2.2, 2.2, 2.2, 2.2, 4.8)
)

По сути, я хочу упростить исходные необработанные данные и перестроить их в табличные данные в виде матриц, чтобы упростить вычисления для среднегодового значения precip для каждой координаты сетки. Для упрощения и эффективности в ожидаемом конечном результате я хочу иметь все столбцы long, lat и annual_mn_precip соответственно.

Как я могу получить эти данные для упрощения и преобразования в R? Есть ли более простой способ сделать это? Спасибо

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Отличительными характеристиками этой проблемы являются:

  1. Одна запись заголовка в день с переменным числом наблюдений / подробных записей в день
  2. Строки наблюдения с изменяющимися подробностями не включают ключ, который связывает заголовки с подробной информацией
  3. Записи заголовка имеют 4 столбца, подробные записи имеют 5 столбцов
  4. Поскольку координата долготы может содержать до 3 цифр слева от десятичной запятой, мы не можем проанализировать записи для пробела в первом столбце, чтобы отличить записи заголовка от подробных записей

Самый простой способ прочитать этот файл и выровнять информацию заголовка с деталями - использовать обработку текста для преобразования файла, чтобы он содержал одно наблюдение на запись. Как только необработанные данные преобразуются, их можно легко прочитать с помощью read.table().

Требуемые преобразования должны быть выполнены в базе R с комбинацией readLines() и lapply().

inFile <- "./data/tempdata1980.txt"
outputFile <- "./data/tempData.txt"
# delete output file if it already exists
if (file.exists(outputFile)) file.remove(outputFile)
theText <- readLines(inFile)
header <- NULL # scope to retain header across executions of lapply()
theResult <- lapply(theText,function(x){
     # reduce blanks to 1 between tokens 
     aRow <- unlist(strsplit(gsub("^ *|(?<= ) | *$", "", x, perl = TRUE)," "))
     # use <<- form of assignment operator to set to parent of if() environment 
     if (length(aRow) == 4) header <<- x
     else {
          cat(paste(header,x),file=outputFile,
              sep="\n",append=TRUE)
     }
})
# now read with read.table
colNames <- c("year","month","day","dayOfYear","long","lat","precip","err1","err2")
theData <- read.table(outputFile,header=FALSE,col.names = colNames)

... и вывод:

> head(theData)
  year month day dayOfYear  long    lat precip  err1 err2
1 1980     1   1         1 6.125 47.375    0.0 20.00    1
2 1980     1   1         1 6.375 47.375    0.0 19.99    1
3 1980     1   1         1 6.625 47.375    0.0 19.97    1
4 1980     1   1         1 6.875 47.375    0.0 19.84    1
5 1980     1   1         1 7.125 47.375    0.0 20.00    1
6 1980     1   2         2 6.125 47.375    1.5 20.00    1
>

Этот подход также позволяет избежать необходимости отслеживать количество наблюдений в день, чтобы объединить записи заголовков с правильным количеством записей подробностей.

ОБНОВЛЕНИЕ: повышение производительности решения

Согласно комментариям к этому ответу, выполнение сценария занимает значительное время в сравнении с полными данными, указанными в OP. Файл необработанных данных содержит 407 705 строк: 365 записей заголовков и 407 340 записей подробностей. Описанное выше решение преобразует данные и загружает их в кадр данных примерно за 155 секунд на MacBook Pro со следующей конфигурацией.

  • Операционная система: OS X Yosemite 10.10.4 (14E46)
  • Процессор: Intel i5 с тактовой частотой 2,6 ГГц, с турбонаддувом до 3,3 ГГц, два ядра
  • Память: 8 гигабайт
  • Диск: 512 гигабайт, твердотельный накопитель
  • Дата постройки: апрель 2013

Причины медленной работы

По сравнению с другим ответом на этот пост существует два потенциальных источника медлительности, в том числе:

  1. Использование строковых функций gsub() и strsplit(), одна из которых генерирует список строк в качестве вывода
  2. Использование cat(...,append=TRUE) в цикле, что означает, что R должен открыть файл, перейти к концу и добавить содержимое более 400 000 раз.

Оптимизация производительности

Мы скорректировали код следующими способами, чтобы улучшить его производительность.

  1. Использовал библиотеку readr для чтения и записи, потому что она работает намного быстрее, чем базовые функции R
  2. Записать вывод из lapply() в вектор в памяти вместо записи на диск во время выполнения lapply() и
  3. Записать выходной вектор один раз с помощью readr::write_lines()

Обновленная версия запускается примерно через 23 секунды, что значительно лучше оригинала. Измененный код и время выполнения приведены ниже.

inFile <- "./data/25_krig_all_1980.txt"
outputFile <- "./data/tempData.txt"
if (file.exists(outputFile)) file.remove(outputFile)
library(readr)
system.time(theText <- readLines(inFile))
#   user  system elapsed 
#  1.821   0.027   1.859 

header <- NULL # scope to retain header across executions of lapply()
outVector <- NULL
i <- 1 
system.time(theResult <- lapply(theText,function(x){
     # reduce blanks to 1 between tokens 
     aRow <- unlist(strsplit(gsub("^ *|(?<= ) | *$", "", x, perl = TRUE)," "))
     # use <<- form of assignment operator to set to parent of if() environment 
     if (length(aRow) == 4) header <<- x
     else {
          outVector[i] <<- paste(header,x)
          i <<- i + 1
     }
}))
#   user  system elapsed 
# 19.327   0.085  19.443 

# write to file
system.time(write_lines(outVector,outputFile))
#   user  system elapsed 
#  0.079   0.020   0.117 

# now read with read.table
colNames <- c("year","month","day","dayOfYear","long","lat","precip","err1","err2")
system.time(theData <- read_table2(outputFile,col_names = colNames))
#  user  system elapsed 
# 0.559   0.071   0.794

Одна финальная оптимизация

Другой ответ ОП проверил, был ли первый столбец записи пустым, чтобы определить, является ли запись заголовком или подробной записью. Вверху моего поста я отметил, что, поскольку долгота может иметь 3 цифры до десятичного знака, эта техника рискованна.

Однако оказывается, что эти данные не имеют долготы больше 100 градусов к востоку или западу от основного меридиана, поэтому мы можем улучшить производительность нашего скрипта во время выполнения с помощью следующего кода.

header <- NULL # scope to retain header across executions of lapply()
outVector <- NULL
i <- 1
system.time(theResult <- lapply(theText,function(x){
     # use <<- form of assignment operator to set to parent of if() environment 
     if (substr(x,1,1) != " ") header <<- x
     else {
          outVector[i] <<- paste(header,x)
          i <<- i + 1
     }
}))
#   user  system elapsed 
#  2.840   0.080   2.933 

Как видно из временных характеристик производительности, замена strsplit() простым сравнением подстрок увеличивает время выполнения шага lapply() с почти 20 секунд до примерно 3 секунд.

0 голосов
/ 04 мая 2018

Вы можете использовать readLines для считывания необработанного текстового файла в вектор строк в файле. Затем вы можете определить, какие строки содержат даты, а какие - наблюдения (в данном случае на основе отступа); читать их в отдельные кадры данных; и объединить фреймы данных на основе индексов строк, содержащих даты. Вот код для этого:

parse_weather <- function(file) {
  lines <- readLines(file)

  # Indicators for whether a line contains a date or an observation
  date_lines <- !startsWith(lines, " ")
  data_lines <- !date_lines

  # Number of observations for each date
  nobs <- diff(c(which(date_lines), length(lines) + 1)) - 1

  dates <- read.table(
    # repeat date for each observation
    text = paste(rep(lines[date_lines], nobs), collapse = "\n"),
    col.names = c("year", "month", "day", "days")
  )

  observations <- read.table(
    text = paste(lines[data_lines], collapse = "\n"),
    col.names = c("long", "lat", "precip", "err1", "err2")
  )

  cbind(dates, observations)
}

# I saved the example data snippet as a local text file
weather <- parse_weather("weather.txt")
head(weather, 8)
#>   year month day days  long    lat precip  err1 err2
#> 1 1980     1   1    1 6.125 47.375    0.0 20.00    1
#> 2 1980     1   1    1 6.375 47.375    0.0 19.99    1
#> 3 1980     1   1    1 6.625 47.375    0.0 19.97    1
#> 4 1980     1   1    1 6.875 47.375    0.0 19.84    1
#> 5 1980     1   1    1 7.125 47.375    0.0 20.00    1
#> 6 1980     1   2    2 6.125 47.375    1.5 20.00    1
#> 7 1980     1   2    2 6.375 47.375    1.5 19.99    1
#> 8 1980     1   2    2 6.625 47.375    1.5 19.97    1

Вероятно, легче работать с этими данными в длинном формате, в котором эта стратегия импорта оставляет их. Если вы хотите иметь столбец на каждый день, вы можете добиться этого, изменив форму данных, например, с помощью. tidyr::spread или rehsape2::dcast.

Редактировать: Оказывается, что read.table очень медленный с большим векторным вводом, заданным в качестве аргумента text. Вставка вектора lines в одну строку значительно ускоряет процесс обработки больших файлов: соответственно я обновил ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...