Вот решение, в котором используются пакеты readxl
и tidyr
из Tidyverse .Чтобы сделать сценарий воспроизводимым, я создал версию снимка экрана OP для Excel и сохранил ее в своем репозитории stackoverflowAnswers
github.Сценарий загружает файл Excel, читает его и преобразует в формат данных Tidy.
# download Excel file from github repository
sourceFile <- "https://raw.githubusercontent.com/lgreski/stackoverflowanswers/master/data/soQuestion53446800.xlsx"
destinationFile <- "./soQuestion53446800.xlsx"
download.file(sourceFile,destinationFile,mode="wb")
library(readxl)
library(tidyr)
# set constants
typeOfLeave <- "sick"
group <- "self employed"
# read date and extract the value
theDate <- read_excel(destinationFile,range="A2:A2",col_names=FALSE)[[1]]
# setup column names using underscore so we can separate key column into Sex and Age columns
theCols <- c("Country","both_all","women_all","men_all","both_up to 17","women_up to 17","men_up to 17")
theData <- read_excel(destinationFile,range="A5:G9",col_names=theCols)
# use tidyr / dplyr to transform the data
theData %>% gather(.,key="key",value="Amount",2:7) %>% separate(.,key,into=c("Sex","Age"),sep="_") -> tidyData
# assign constants
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$date <- theDate
tidyData
... и вывод:
> tidyData
# A tibble: 30 x 7
Country Sex Age Amount typeOfLeave group date
<chr> <chr> <chr> <dbl> <chr> <chr> <dttm>
1 Total both all 151708 sick self employed 2016-03-31 00:00:00
2 Afganistan both all 269 sick self employed 2016-03-31 00:00:00
3 Albania both all 129 sick self employed 2016-03-31 00:00:00
4 Algeria both all 308 sick self employed 2016-03-31 00:00:00
5 Andora both all 815 sick self employed 2016-03-31 00:00:00
6 Total women all 49919 sick self employed 2016-03-31 00:00:00
7 Afganistan women all 104 sick self employed 2016-03-31 00:00:00
8 Albania women all 30 sick self employed 2016-03-31 00:00:00
9 Algeria women all 18 sick self employed 2016-03-31 00:00:00
10 Andora women all 197 sick self employed 2016-03-31 00:00:00
# ... with 20 more rows
Ключевые элементы в решении
Microsoft Excel часто используется в качестве инструмента ввода данных и отчетности, которыйзаставляет людей структурировать свои таблицы в форматах иерархических таблиц, как показано в OP.Этот формат затрудняет использование данных в R, поскольку имена столбцов представляют комбинации информации, которая отображается иерархически в заголовках таблиц в электронной таблице.
В этом разделе мы объясним некоторые из ключевых элементов дизайна в решении проблемы, поставленной в OP, включая:
- Чтение файлов Excel через точные ссылки на ячейки с помощью
readxl::read_excel()
- Считывание одной ячейки в константу
- Настройка имен столбцов для удобства использования с
tidyr::separate()
- Реструктуризация в узкий формат Tidy Data
- Назначение констант
1.Чтение точных ссылок на ячейки
В вопросе OP отмечается, что существует строка заголовка, содержащая дату для всех ячеек в конкретной таблице.Чтобы смоделировать это в примере электронной таблицы, которую я использовал для копирования снимка экрана в OP, я назначил дату 31 марта 2016 года ячейке A2
из Sheet 1
в книге Excel.
readxl::read_excel()
позволяет читать точные ссылки на ячейки с аргументом range=
.
2.Считывание константы из одной ячейки
Если мы установим аргумент range=
в одну ячейку и извлечем ячейку с помощью формы [[
оператора извлечения, то получающийся объект будет одним элементом вектора вместофрейм данных.Это позволяет использовать векторную переработку для присвоения этого значения фрейму аккуратных данных позже в сценарии R.
Поскольку все в R является объектом, мы можем использовать оператор извлечения [[
для результата read_excel()
, чтобы присвоить результат theDate
.
theDate <- read_excel(theXLSX,range="A2:A2",col_names=FALSE)[[1]]
3.Установка имен столбцов для удобства использования с tidyr::separate()
Одной из характеристик, которая делает исходную электронную таблицу грязной, в отличие от Tidy Data , является тот факт, что каждый столбец данных представляет собой комбинациюSex
и Age
значения.
Требуемый фрейм выходных данных включает столбцы для Sex
и Age
, и поэтому нам нужен способ извлечь эту информацию из имен столбцов.Пакет tidyr
предоставляет функцию для поддержки этой техники, функцию separate()
.
Чтобы упростить использование этой функции, мы присваиваем имена столбцов с разделителем подчеркивания, чтобы различать компоненты Sex
и Age
в именах столбцов.
theCols <- c("Country","both_all","women_all","men_all","both_up to 17","women_up to 17","men_up to 17")
4.Реструктуризация данных в узкий формат Tidy Data
Ключевым шагом в скрипте является последовательность функций Tidyverse, которая принимает кадр данных, считанный с помощью read_excel()
, использует tidyr::gather()
в столбцах 2 - 7 для создания одной строкидля каждой уникальной комбинации Страна, Пол и Возраст, а затем разбивает полученный столбец key
на столбцы Sex
и Age
.
theData %>% gather(.,key="key",value="Amount",2:7) %>% separate(.,key,into=c("Sex","Age"),sep="_") -> tidyData
Данные слева от подчеркивания назначаются столбцу Sex
, а справа от подчеркивания - Age
.Обратите внимание, что OP не определяет, как итоги должны обрабатываться в выходных данных.Поскольку total
не имеет смысла в качестве значения для Sex
, я использовал вместо него Both
.Точно так же для Age
я назначил total
как All
.
5.Назначение констант
ОП не объясняет, откуда взяты константы sick
и group
, поэтому я назначил их в качестве констант в начале программы.Если они включены в иерархическую часть электронной таблицы, их можно легко прочитать, используя технику, которую я использовал для извлечения даты из электронной таблицы.
После того, как данные представлены в аккуратном формате, мы добавляем оставшиеся константы через оператор присваивания, используя векторную переработку в R.
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$date <- theDate
Дополнительные замечания
Если значения total
не требуются во фрейме выходных данных, их можно легко удалить с помощью оператора извлечения аккуратных данных или отбрасывания столбцов из фрейма грязных данных перед использованием gather()
.
Обратите внимание, что я решил оставить итоги во фрейме выходных данных, поскольку почти все данные на снимке экрана представляют итоги той или иной формы (т. Е. Только 2 из 30 ячеек данных на экране OP).захват не был итоговым), и удаление этих данных затруднило бы подтверждение того, что скрипт работал правильно.
Решение можно расширить, чтобы охватить возрастные категории, на которые есть ссылки в ОП, но не показано в электронной таблице, добавив соответствующие имена столбцов в вектор theCols
и изменив аргумент range=
в функции read_excel()
это читает большую часть таблицы.
ОБНОВЛЕНИЕ: чтение нескольких кварталов с определенного листа
29 ноября оригинальный постер изменил вопрос, объяснив, что в файле Excel было несколько листов, по одному на каждый год.Это легко сделать с помощью следующих модификаций:
- Укажите рабочий лист с параметром
sheet=
- Добавьте
_Q1
, чтобы различать чтение каждого квартала и сохранить квартал в качестве ключа.переменная - Установить имена рабочих листов в годы
Полученные в результате аккуратные данные будут иметь столбцы года и квартала.Обратите внимание, что я обновил свою рабочую книгу Excel фиктивными данными, чтобы рабочие таблицы, представляющие разные годы, имели разные данные, чтобы результаты были различимыми.
# download file from github to make script completely reproducible
sourceFile <- "https://raw.githubusercontent.com/lgreski/stackoverflowanswers/master/data/soQuestion53446800.xlsx"
destinationFile <- "./soQuestion53446800.xlsx"
download.file(sourceFile,destinationFile,mode="wb")
# set constants
typeOfLeave <- "sick"
group <- "self employed"
year <- "2018"
# setup column names using underscore so we can separate key column into Sex, Age, and Quarter columns
# after using rep() to build data with required repeating patterns, avoiding manual typing of all the column names
sex <- rep(c("both","women","men"),16)
age <- rep(c(rep("all",3),rep("up to 17",3),rep("18 to 64",3),rep("65 and over",3)),4)
quarter <- c(rep("Q1",12),rep("Q2",12),rep("Q3",12),rep("Q4",12))
data.frame(sex,age,quarter) %>% unite(excelColNames) -> columnsData
theCols <- unlist(c("Country",columnsData["excelColNames"]))
theData <- read_excel(destinationFile,sheet=year,range="A5:AW9",col_names=theCols)
# use tidyr / dplyr to transform the data
theData %>% gather(.,key="key",value="Amount",2:49) %>% separate(.,key,into=c("Sex","Age","Quarter"),sep="_") -> tidyData
# assign constants
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$year <- year
tidyData
... и выходные данные, считанные из листа 2018 в рабочей книге.
> tidyData
# A tibble: 240 x 8
Country Sex Age Quarter Amount typeOfLeave group year
<chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
1 Total both all Q1 2100 sick self employed 2018
2 Afganistan both all Q1 2100 sick self employed 2018
3 Albania both all Q1 2100 sick self employed 2018
4 Algeria both all Q1 2100 sick self employed 2018
5 Andora both all Q1 2100 sick self employed 2018
6 Total women all Q1 900 sick self employed 2018
7 Afganistan women all Q1 900 sick self employed 2018
8 Albania women all Q1 900 sick self employed 2018
9 Algeria women all Q1 900 sick self employed 2018
10 Andora women all Q1 900 sick self employed 2018
# ... with 230 more rows
>
Если мы изменим параметры конфигурации, мы сможем прочитать данные за 2017 год из рабочей книги Iотправил на Github.
# read second worksheet to illustrate multiple reads
# set constants
typeOfLeave <- "sick"
group <- "self employed"
year <- "2017"
theData <- read_excel(destinationFile,sheet=year,range="A5:AW9",col_names=theCols)
# use tidyr / dplyr to transform the data
theData %>% gather(.,key="key",value="Amount",2:49) %>% separate(.,key,into=c("Sex","Age","Quarter"),sep="_") -> tidyData
# assign constants
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$year <- year
tidyData
... и вывод:
> tidyData
# A tibble: 240 x 8
Country Sex Age Quarter Amount typeOfLeave group year
<chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
1 Total both all Q1 33000 sick self employed 2017
2 Afganistan both all Q1 33000 sick self employed 2017
3 Albania both all Q1 33000 sick self employed 2017
4 Algeria both all Q1 33000 sick self employed 2017
5 Andora both all Q1 33000 sick self employed 2017
6 Total women all Q1 15000 sick self employed 2017
7 Afganistan women all Q1 15000 sick self employed 2017
8 Albania women all Q1 15000 sick self employed 2017
9 Algeria women all Q1 15000 sick self employed 2017
10 Andora women all Q1 15000 sick self employed 2017
# ... with 230 more rows
>
Собираем все вместе ...
На этом этапе мы встроили основные идеи в сценарийэто полностью читает один лист.Если мы немного изменим код и включим такую функцию, как lapply()
, мы можем начать с вектора имен рабочих листов, прочитать файлы, преобразовать их в формат аккуратных данных и объединить файлы в один набор аккуратных данных с помощью do.call()
и rbind()
.
## version that combines multiple years into a single narrow format tidy data file
# download file from github to make script completely reproducible
sourceFile <- "https://raw.githubusercontent.com/lgreski/stackoverflowanswers/master/data/soQuestion53446800.xlsx"
destinationFile <- "./soQuestion53446800.xlsx"
download.file(sourceFile,destinationFile,mode="wb")
library(readxl)
library(tidyr)
# set constants
years <- c("2017","2018")
typeOfLeave <- "sick"
group <- "self employed"
# setup column names using underscore so we can separate key column into Sex, Age, and Quarter columns
# after using rep() to build data with required repeating patterns, avoiding manual typing of all the column names
sex <- rep(c("both","women","men"),16)
age <- rep(c(rep("all",3),rep("up to 17",3),rep("18 to 64",3),rep("65 and over",3)),4)
quarter <- c(rep("Q1",12),rep("Q2",12),rep("Q3",12),rep("Q4",12))
data.frame(sex,age,quarter) %>% unite(excelColNames) -> columnsData
theCols <- unlist(c("Country",columnsData["excelColNames"]))
lapply(years,function(x){
theData <- read_excel(destinationFile,sheet=x,range="A5:AW9",col_names=theCols)
# use tidyr / dplyr to transform the data
theData %>% gather(.,key="key",value="Amount",2:49) %>% separate(.,key,into=c("Sex","Age","Quarter"),sep="_") -> tidyData
# assign constants
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$year <- x
tidyData
}) %>% do.call(rbind,.) -> combinedData
... и выходные данные, демонстрирующие, что фрейм данных combinedData
содержит данные рабочих таблиц как 2017, так и 2018 года.
> head(combinedData)
# A tibble: 6 x 8
Country Sex Age Quarter Amount typeOfLeave group year
<chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
1 Total both all Q1 33000 sick self employed 2017
2 Afganistan both all Q1 33000 sick self employed 2017
3 Albania both all Q1 33000 sick self employed 2017
4 Algeria both all Q1 33000 sick self employed 2017
5 Andora both all Q1 33000 sick self employed 2017
6 Total women all Q1 15000 sick self employed 2017
> tail(combinedData)
# A tibble: 6 x 8
Country Sex Age Quarter Amount typeOfLeave group year
<chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
1 Andora women 65 and over Q4 2300 sick self employed 2018
2 Total men 65 and over Q4 2400 sick self employed 2018
3 Afganistan men 65 and over Q4 2400 sick self employed 2018
4 Albania men 65 and over Q4 2400 sick self employed 2018
5 Algeria men 65 and over Q4 2400 sick self employed 2018
6 Andora men 65 and over Q4 2400 sick self employed 2018
>