我正在努力进行一些数据转换。我有一些带有保险数据的巨大xlsx文件。数据的结构有点像“金字塔”。第一行代表进行调查的季度。下一行是按年龄分类的细分。共有4个类别:总价值最高为17、18-64和65+。一张纸包含四个季度,因此基本上有48个唯一变量和带有国家/地区名称的列。一个excel文件包含3张纸(2016、2017和2018)。屏幕截图(输入数据)来自一个excel文件,其名称为“病假蓝领工人”。我还有另外两个文件:“病假工人”和“病假自雇”。目标是合并所有三个文件,并使用结果数据中的结构创建一个文件。你能帮我吗?
答案 0 :(得分:2)
这是一个使用Tidyverse中的readxl
和tidyr
软件包的解决方案。为了使脚本可复制,我创建了OP屏幕截图的Excel版本并将其保存到我的stackoverflowAnswers
github存储库中。该脚本下载Excel文件,进行读取,然后将其转换为Tidy Data格式。
# 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中提出的问题的解决方案中的一些关键设计元素,包括:
readxl::read_excel()
通过精确的单元格引用读取Excel文件tidyr::separate()
一起使用 OP问题指出,存在一个标题行,其中包含特定表中所有单元格的日期。为了在用于复制OP中的屏幕快照的示例电子表格中进行模拟,我将2016年3月31日的日期分配给Excel工作簿中A2
的单元格Sheet 1
。
readxl::read_excel()
允许使用range=
参数读取精确的单元格引用。
如果我们将range=
参数设置为单个单元格并以[[
形式的extract运算符提取单元格,则结果对象是单个元素向量,而不是数据帧。这样就可以在稍后的R脚本中使用向量循环来将此值分配给整洁的数据帧。
由于R中的所有内容都是对象,因此我们可以对[[
的结果使用read_excel()
提取运算符将结果分配给theDate
。
theDate <- read_excel(theXLSX,range="A2:A2",col_names=FALSE)[[1]]
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")
脚本中的关键步骤是一系列Tidyverse函数,该函数获取用read_excel()
读取的数据帧,在第2-7列上使用tidyr::gather()
为每个国家/地区,性别的唯一组合创建一行以及年龄,然后将所得的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
。
OP没有解释常量sick
和group
的来源,因此我在程序开始时将它们分配为常量。如果这些内容包含在电子表格的层次结构部分中,则可以使用我用来从电子表格中提取日期的技术轻松地读取它们。
一旦数据采用整洁的格式,我们将利用R中的vector recycling通过赋值运算符添加剩余的常量。
tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$date <- theDate
如果在输出数据帧中不需要total
值,则可以通过使用整洁数据上的提取运算符或在使用{{1}之前从混乱数据帧中删除列来轻松消除它们}。
请注意,我选择将总计保留在输出数据帧中,因为屏幕截图中的几乎所有数据都代表一种或另一种形式的总计(即,OP屏幕截图中的30个数据单元中只有2个不是总计),并且消除这些数据将使难以确认脚本正确运行。
通过向gather()
向量中添加适当的列名,并通过更改{{1中的theCols
自变量,可以将该解决方案扩展到涵盖OP中引用但未在电子表格中显示的年龄类别。 }}函数可读取电子表格的大部分内容。
11月29日,原始张贴者修改了问题,以解释Excel文件中有多个工作表,每年一个。通过以下修改,可以轻松解决此问题。
range=
参数指定工作表read_excel()
以区分每个季度的读数,并将该季度保存为关键变量结果整洁的数据将包含年份和季度列。请注意,我用虚拟数据更新了Excel工作簿,因此代表不同年份的工作表具有不同的数据,因此结果是可区分的。
sheet=
...以及输出,从工作簿中的2018年工作表中读取。
_Q1
如果更改配置参数,则可以从我发布到Github的工作簿中读取2017年数据。
# 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
...以及输出:
> 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
>
这时,我们已经将基本思想构建到一个脚本中,该脚本可以完全读取一个工作表。如果我们稍加修改代码并加入诸如# 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()
数据框包含来自2017年和2018年工作表的数据。
## 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