#r #regex #text #dataframe
#r #регулярное выражение #текст #фрейм данных
Вопрос:
У меня есть серия текстовых файлов, которые выглядят следующим образом:
USABILITY DATA
Level Name: LVL_Introduction
Time Spent: 323.233307
Times Teleported: 6
NumDeaths: 0
Level Name: LVL_1-1
Time Spent: 36.760727
Times Teleported: 1
NumDeaths: 0
Level Name: LVL_1-2
Time Spent: 45.953598
Times Teleported: 1
NumDeaths: 0
Level Name: LVL_1-3
Time Spent: 176.440704
Times Teleported: 0
NumDeaths: 0
Level Name: LVL_1-4
Time Spent: 281.797485
Times Teleported: 0
NumDeaths: 0
Каждый текстовый файл представляет данные одного игрока из игрового сеанса в головоломке от уровня к уровню. Некоторые игроки достигают разных уровней, чем другие.
Я хотел бы преобразовать этот текстовый файл в фрейм данных, который дал бы мне таблицу с именем уровня (например: «LVL_1-3») в качестве заголовка столбца и «Затраченное время», «Время телепортирования» и «NumDeaths» в качестве заголовков строк.
LVL_Introduction Lvl_1-1 etc...
Time Spent: 323.233307 36.760727
Times Teleported: 6 1
NumDeath: 0 0
В идеале сценарий должен быть достаточно надежным, чтобы работать независимо от того, какой пользователь (или имя уровня) задействован. Я видел несколько разных решений для связанных проблем, некоторые с использованием регулярных выражений, а некоторые просто разделяют строки. Я не уверен, что лучше всего использовать одну строку для создания как заголовка, так и значения данных в скрипте, который работает с несколькими файлами.
Спасибо, Митчелл
Комментарии:
1. Совпадают ли значения «Имя уровня» во всех текстовых файлах? Кроме того, вы уверены, что именно так вы хотите структурировать выходной фрейм данных? Кажется, что было бы более уместно иметь столбцы что-то вроде
c("Player","Level","Time.Spent","Times.Teleported","NumDeath")
2. Существует основной набор возможных значений, но значения могут отличаться от текстового файла к текстовому файлу, как и количество записей уровня. Уровни могут быть (от LVL_1-1 до LVL_1-5) вплоть до (от LVL_4-1 до LVL_4-5), потому что существует 4 набора по 5 уровней. Существует вводный уровень и главное меню. Разные игроки достигают разных точек в игре, поэтому каждый файл отличается.
3. @rosscova Я думал, что мог бы расплавить этот фрейм данных, чтобы у меня был каждый пользователь в виде отдельного столбца, но для этого потребовались бы имена строк, подобные этим: Time_Intro Teleports_Intro Deaths_Intro Time_1-1 Teleports_1-1 Deaths_1-1 Time_1-2 Teleports_1-2 Deaths_1-2 Time_1-3 Teleports_1-3 Deaths_1-3 Time_1-4 Teleports_1-4 Deaths_1-4 Time_1-5 Teleports_1-5 Deaths_1-5 Time_Menu Teleports_Menu Deaths_Menu Time_2-1 Teleports_2-1 Deaths_2-1
4. Надеюсь, это имеет смысл — есть 17 возможных уровней игры, 3 переменные, которые я измеряю, и много разных пользователей. Я могу использовать переменные x уровней или «переменную по уровню» x пользователя. Было бы проще вручную ввести данные в чистый файл .csv, я просто хотел посмотреть, существует ли решение R.
Ответ №1:
Вот мое предложение, хотя оно не совсем соответствует тому, что вы просили, я думаю, вы обнаружите, что оно предоставляет вам ваши данные в очень аккуратном (читай: полезном) виде:
library( readr )
library( plyr )
import <- function( datafile.name ) {
new <- read_delim( datafile.name,
col_names = c( "category", "data" ),
delim = ":",
trim_ws = TRUE )
new <- new[ !is.na( new$data ), ]
output <- data.frame(
new[ new$category == "Level Name", "data" ],
new[ new$category == "Time Spent", "data" ],
new[ new$category == "Times Teleported", "data" ],
new[ new$category == "NumDeaths", "data" ],
stringsAsFactors = FALSE
)
names( output ) <- c( "level.name", "time.spent", "times.teleported", "num.deaths" )
# get the username from the file name
output$user <- datafile.name
return( output )
}
# get a list of files
setwd( [where your files are located] )
filelist <- list.files()
# and apply the function above to all those files to create one big dataframe
df <- ldply( .data = filelist,
.fun = import )
Примечание, я бы предположил, что ваши имена файлов напрямую не представляют имена пользователей, поэтому вам нужно будет выполнить преобразование. Достаточно чего-то настолько простого, насколько gsub(".txt","",datafile.name)
это возможно.
Если требуется импортировать много файлов, вы также можете захотеть выполнить этот процесс в нескольких потоках. Здесь он находится на 4 ядрах:
library( doMC )
registerDoMC( cores = 4 )
df <- ldply( .data = filelist,
.fun = import,
.parallel = TRUE )
Комментарии:
1. Спасибо за предложения! Я попробую их 🙂
Ответ №2:
readr
имеет приятную функцию чтения с фрагментами, которая может помочь, если у вас есть фрагментированные, отформатированные данные.
Здесь я создаю функцию для обработки фрагментов — она используется read.delim
для чтения variable: value
данных, а затем транспонирует их с помощью tidyr::spread
library(tidyr)
library(readr)
f <- function(x, pos) {
dat <- read.delim(text = x, sep = ":", header = FALSE, stringsAsFactors = FALSE)
return(spread(dat, V1, V2))
}
чтение фрагментов с DataFrameCallback
помощью дает data.frame
результаты через rbind
rawData <- read_lines_chunked(file = "gamedata.txt",
skip = 1,
chunk_size = 5,
callback = DataFrameCallback$new(f))
тогда остается только переформатировать это в соответствии с тем, что вы просили (использование 4
для идентификатора столбца ненадежно, но это чистый пример)
gameData <- setNames(data.frame(t(rawData[, -4])), rawData[, 4])[c(2,3,1), ]
gameData
#> LVL_Introduction LVL_1-1 LVL_1-2 LVL_1-3
#> Time Spent 323.233307 36.760727 45.953598 176.440704
#> Times Teleported 6 1 1 0
#> NumDeaths 0 0 0 0
#> LVL_1-4
#> Time Spent 281.797485
#> Times Teleported 0
#> NumDeaths 0