Чтение Excel в R: как найти начальную ячейку в грязных таблицах
Я пытаюсь написать R-код для чтения данных из беспорядка старых электронных таблиц. Точное местоположение данных варьируется от листа к листу: единственная константа - это то, что первый столбец является датой, а второй столбец имеет "Ежемесячный возврат" в качестве заголовка. В этом примере данные начинаются в ячейке B5:
![образец таблицы]()
Как автоматизировать поиск ячеек Excel для моей строки "Monthly return" с помощью R?
На данный момент, лучшая идея, которую я могу придумать, - загрузить все в R, начиная с ячейки A1, и разобраться в беспорядке в получающихся (огромных) матрицах. Я надеюсь на более элегантное решение
Ответы
Ответ 1
Я не нашел способ сделать это изящно, но я очень хорошо знаком с этой проблемой (получение данных из отчетов FactSet PA → Excel → R, правильно?). Я понимаю, что разные отчеты имеют разные форматы, и это может быть болью.
Для немного отличающейся версии форматированных таблиц с неудобными формами, я делаю следующее. Это не самый элегантный (требуется два чтения файла), но он работает. Мне нравится читать файл дважды, чтобы убедиться, что столбцы имеют правильный тип и хорошие заголовки. Легко испортить импорт столбцов, поэтому я бы предпочел, чтобы мой код дважды читал файл, а затем очищал столбцы, а значения по умолчанию read_excel, если вы начинаете с правой строки, довольно хороши.
Кроме того, стоит отметить, что на сегодняшний день (2017-04-20), readxl имеет обновление. Я установил новую версию, чтобы убедиться, что это будет очень легко, но я не верю этому случаю, хотя я мог ошибаться.
library(readxl)
library(stringr)
library(dplyr)
f_path <- file.path("whatever.xlsx")
if (!file.exists(f_path)) {
f_path <- file.choose()
}
# I read this twice, temp_read to figure out where the data actually starts...
# Maybe you need something like this -
# excel_sheets <- readxl::excel_sheets(f_path)
# desired_sheet <- which(stringr::str_detect(excel_sheets,"2 Factor Brinson Attribution"))
desired_sheet <- 1
temp_read <- readxl::read_excel(f_path,sheet = desired_sheet)
skip_rows <- NULL
col_skip <- 0
search_string <- "Monthly Returns"
max_cols_to_search <- 10
max_rows_to_search <- 10
# Note, for the - 0, you may need to add/subtract a row if you end up skipping too far later.
while (length(skip_rows) == 0) {
col_skip <- col_skip + 1
if (col_skip == max_cols_to_search) break
skip_rows <- which(stringr::str_detect(temp_read[1:max_rows_to_search,col_skip][[1]],search_string)) - 0
}
# ... now we re-read from the known good starting point.
real_data <- readxl::read_excel(
f_path,
sheet = desired_sheet,
skip = skip_rows
)
# You likely don't need this if you start at the right row
# But given that all weird spreadsheets are weird in their own way
# You may want to operate on the col_skip, maybe like so:
# real_data <- real_data %>%
# select(-(1:col_skip))
Ответ 2
Хорошо, в формате был указан для xls, обновлен от csv до корректно предложенной загрузки xls.
library(readxl)
data <- readxl::read_excel(".../sampleData.xls", col_types = FALSE)
Вы получите нечто похожее на:
data <- structure(list(V1 = structure(c(6L, 5L, 3L, 7L, 1L, 4L, 2L), .Label = c("",
"Apr 14", "GROSS PERFROANCE DETAILS", "Mar-14", "MC Pension Fund",
"MY COMPANY PTY LTD", "updated by JS on 6/4/2017"), class = "factor"),
V2 = structure(c(1L, 1L, 1L, 1L, 4L, 3L, 2L), .Label = c("",
"0.069%", "0.907%", "Monthly return"), class = "factor")), .Names = c("V1",
"V2"), class = "data.frame", row.names = c(NA, -7L))
то вы можете динамически фильтровать ячейку "Ежемесячный возврат" и идентифицировать вашу матрицу.
targetCell <- which(data == "Monthly return", arr.ind = T)
returns <- data[(targetCell[1] + 1):nrow(data), (targetCell[2] - 1):targetCell[2]]
Ответ 3
С пакетом общего назначения, например readxl, вам придется читать дважды, если вы хотите использовать автоматическое преобразование типов. Я предполагаю, что у вас есть какая-то верхняя граница по количеству стартовых рядов на фронте? Здесь я предположил, что это было 10. Я повторяю рабочие листы в одной книге, но код будет выглядеть очень похоже, если итерация над книгами. Я бы написал одну функцию для обработки одного рабочего листа или рабочей книги, а затем использовал lapply()
или purrr::map()
. Эта функция будет инкапсулировать прочитанное чтение пропусков и "реальное" чтение.
library(readxl)
two_passes <- function(path, sheet = NULL, n_max = 10) {
first_pass <- read_excel(path = path, sheet = sheet, n_max = n_max)
skip <- which(first_pass[[2]] == "Monthly return")
message("For sheet '", if (is.null(sheet)) 1 else sheet,
"' we'll skip ", skip, " rows.")
read_excel(path, sheet = sheet, skip = skip)
}
(sheets <- excel_sheets("so.xlsx"))
#> [1] "sheet_one" "sheet_two"
sheets <- setNames(sheets, sheets)
lapply(sheets, two_passes, path = "so.xlsx")
#> For sheet 'sheet_one' we'll skip 4 rows.
#> For sheet 'sheet_two' we'll skip 6 rows.
#> $sheet_one
#> # A tibble: 6 × 2
#> X__1 `Monthly return`
#> <dttm> <dbl>
#> 1 2017-03-14 0.00907
#> 2 2017-04-14 0.00069
#> 3 2017-05-14 0.01890
#> 4 2017-06-14 0.00803
#> 5 2017-07-14 -0.01998
#> 6 2017-08-14 0.00697
#>
#> $sheet_two
#> # A tibble: 6 × 2
#> X__1 `Monthly return`
#> <dttm> <dbl>
#> 1 2017-03-14 0.00907
#> 2 2017-04-14 0.00069
#> 3 2017-05-14 0.01890
#> 4 2017-06-14 0.00803
#> 5 2017-07-14 -0.01998
#> 6 2017-08-14 0.00697
Ответ 4
В этих случаях важно знать возможные условия ваших данных. Я предполагаю, что вы хотите удалить только столбцы и строки, которые не конфликтуют с вашей таблицей.
У меня есть эта книга Excel:
![введите описание изображения здесь]()
Я добавил 3 пустых столбца слева, потому что когда я загрузил R в один столбец, программа опускает их. То для подтверждения, что R опускает пустые столбцы слева.
Сначала: данные загрузки
library(xlsx)
dat <- read.xlsx('book.xlsx', sheetIndex = 1)
head(dat)
MY.COMPANY.PTY.LTD NA.
1 MC Pension Fund <NA>
2 GROSS PERFORMANCE DETAILS <NA>
3 updated by IG on 20/04/2017 <NA>
4 <NA> Monthly return
5 Mar-14 0.0097
6 Apr-14 6e-04
Во-вторых: я добавил несколько столбцов с значениями NA
и ''
в том случае, если ваши данные содержат некоторые
dat$x2 <- NA
dat$x4 <- NA
head(dat)
MY.COMPANY.PTY.LTD NA. x2 x4
1 MC Pension Fund <NA> NA NA
2 GROSS PERFORMANCE DETAILS <NA> NA NA
3 updated by IG on 20/04/2017 <NA> NA NA
4 <NA> Monthly return NA NA
5 Mar-14 0.0097 NA NA
6 Apr-14 6e-04 NA NA
В-третьих: удалите столбцы, когда все значения NA
и ''
. Мне приходится иметь дело с такими проблемами в прошлом
colSelect <- apply(dat, 2, function(x) !(length(x) == length(which(x == '' | is.na(x)))))
dat2 <- dat[, colSelect]
head(dat2)
MY.COMPANY.PTY.LTD NA.
1 MC Pension Fund <NA>
2 GROSS PERFORMANCE DETAILS <NA>
3 updated by IG on 20/04/2017 <NA>
4 <NA> Monthly return
5 Mar-14 0.0097
6 Apr-14 6e-04
В-четвертых: сохраняйте только строки с полными наблюдениями (это то, что я полагаю из вашего примера)
rowSelect <- apply(dat2, 1, function(x) !any(is.na(x)))
dat3 <- dat2[rowSelect, ]
head(dat3)
MY.COMPANY.PTY.LTD NA.
5 Mar-14 0.0097
6 Apr-14 6e-04
7 May-14 0.0189
8 Jun-14 0.008
9 Jul-14 -0.0199
10 Ago-14 0.00697
Наконец, если вы хотите сохранить заголовок, вы можете сделать что-то вроде этого:
colnames(dat3) <- as.matrix(dat2[which(rowSelect)[1] - 1, ])
или
colnames(dat3) <- c('Month', as.character(dat2[which(rowSelect)[1] - 1, 2]))
dat3
Month Monthly return
5 Mar-14 0.0097
6 Apr-14 6e-04
7 May-14 0.0189
8 Jun-14 0.008
9 Jul-14 -0.0199
10 Ago-14 0.00697
Ответ 5
Вот как я бы справился с этим.
ШАГ 1
Прочтите таблицу Excel в заголовках without
.
ШАГ 2
Найти индекс строки для строки Monthly return
в этом случае
ШАГ 3
Фильтр из идентифицированной строки (или столбца или и того и другого), немного уменьшите и сделайте.
Вот как выглядит примерная функция. Он работает для вашего примера независимо от того, где он находится в электронной таблице. Вы можете играть с regex
, чтобы сделать его более надежным.
Определение функции:
library(xlsx)
extract_return <- function(path = getwd(), filename = "Mysheet.xlsx", sheetnum = 1){
filepath = paste(path, "/", filename, sep = "")
input = read.xlsx(filepath, sheetnum, header = FALSE)
start_idx = which(input == "Monthly return", arr.ind = TRUE)[1]
output = input[start_idx:dim(input)[1],]
rownames(output) <- NULL
colnames(output) <- c("Date","Monthly Return")
output = output[-1, ]
return(output)
}
Пример:
final_df <- extract_return(
path = "~/Desktop",
filename = "Apr2017.xlsx",
sheetnum = 2)
Неважно, сколько строк или столбцов у вас есть, идея остается той же. Попробуйте и дайте мне знать.
Ответ 6
grep("2014",dat)[1]
Это дает вам первый столбец с годом. Или используйте "-14" или все, что у вас есть в течение многих лет.
Подобным образом grep ( "Monthly", dat) [1] дает вам второй столбец