R-读取Excel文件并将变量切换为观察值

时间:2018-11-23 12:33:20

标签: r excel tidyr

我正在努力进行一些数据转换。我有一些带有保险数据的巨大xlsx文件。数据的结构有点像“金字塔”。第一行代表进行调查的季度。下一行是按年龄分类的细分。共有4个类别:总价值最高为17、18-64和65+。一张纸包含四个季度,因此基本上有48个唯一变量和带有国家/地区名称的列。一个excel文件包含3张纸(2016、2017和2018)。屏幕截图(输入数据)来自一个excel文件,其名称为“病假蓝领工人”。我还有另外两个文件:“病假工人”和“病假自雇”。目标是合并所有三个文件,并使用结果数据中的结构创建一个文件。你能帮我吗?

输入数据:enter image description here

结果数据:enter image description here

1 个答案:

答案 0 :(得分:2)

这是一个使用Tidyverse中的readxltidyr软件包的解决方案。为了使脚本可复制,我创建了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中提出的问题的解决方案中的一些关键设计元素,包括:

  1. 使用readxl::read_excel()通过精确的单元格引用读取Excel文件
  2. 将单个单元格读取为常量
  3. 设置列名以方便与tidyr::separate()一起使用
  4. 重组为窄格式的整洁数据
  5. 分配常量

1。读取确切的单元格引用

OP问题指出,存在一个标题行,其中包含特定表中所有单元格的日期。为了在用于复制OP中的屏幕快照的示例电子表格中进行模拟,我将2016年3月31日的日期分配给Excel工作簿中A2的单元格Sheet 1

readxl::read_excel()允许使用range=参数读取精确的单元格引用。

2。从一个单元格读取常量

如果我们将range=参数设置为单个单元格并以[[形式的extract运算符提取单元格,则结果对象是单个元素向量,而不是数据帧。这样就可以在稍后的R脚本中使用向量循环来将此值分配给整洁的数据帧。

由于R中的所有内容都是对象,因此我们可以对[[的结果使用read_excel()提取运算符将结果分配给theDate

theDate <- read_excel(theXLSX,range="A2:A2",col_names=FALSE)[[1]]

3。设置列名称以方便使用tidyr::separate()

使原始电子表格变得混乱而不是Tidy Data的特征之一是,每一列数据都代表SexAge值的组合。

所需的输出数据帧包括SexAge的列,因此,我们需要一种从列名中提取此信息的方法。 tidyr软件包提供了一种支持此技术的功能,即separate()函数。

为方便使用此功能,我们为列名称分配了下划线分隔符,以区分列名称中的SexAge组件。

    theCols <- c("Country","both_all","women_all","men_all","both_up to 17","women_up to 17","men_up to 17")

4。将数据重组为窄格式的整洁数据

脚本中的关键步骤是一系列Tidyverse函数,该函数获取用read_excel()读取的数据帧,在第2-7列上使用tidyr::gather()为每个国家/地区,性别的唯一组合创建一行以及年龄,然后将所得的key列拆分为SexAge列。

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。分配常量

OP没有解释常量sickgroup的来源,因此我在程序开始时将它们分配为常量。如果这些内容包含在电子表格的层次结构部分中,则可以使用我用来从电子表格中提取日期的技术轻松地读取它们。

一旦数据采用整洁的格式,我们将利用R中的vector recycling通过赋值运算符添加剩余的常量。

tidyData$typeOfLeave <- typeOfLeave
tidyData$group <- group
tidyData$date <- theDate

其他注意事项

如果在输出数据帧中不需要total值,则可以通过使用整洁数据上的提取运算符或在使用{{1}之前从混乱数据帧中删除列来轻松消除它们}。

请注意,我选择将总计保留在输出数据帧中,因为屏幕截图中的几乎所有数据都代表一种或另一种形式的总计(即,OP屏幕截图中的30个数据单元中只有2个不是总计),并且消除这些数据将使难以确认脚本正确运行。

通过向gather()向量中添加适当的列名,并通过更改{{1中的theCols自变量,可以将该解决方案扩展到涵盖OP中引用但未在电子表格中显示的年龄类别。 }}函数可读取电子表格的大部分内容。

更新:从特定工作表中读取多个季度

11月29日,原始张贴者修改了问题,以解释Excel文件中有多个工作表,每年一个。通过以下修改,可以轻松解决此问题。

  1. 使用range=参数指定工作表
  2. 添加read_excel()以区分每个季度的读数,并将该季度保存为关键变量
  3. 将工作表名称设置为年份

结果整洁的数据将包含年份和季度列。请注意,我用虚拟数据更新了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