数据的获取途径主要可分为两大类:通过直接调查获得的一手数据,以及利用现有资源的二手数据。直接调查是获取一手数据的主要方式,其优势在于研究者能够根据具体研究问题和假设,自主设计数据收集方案,从而获得更具针对性的数据。常见的调查方法包括:
问卷调查:通过精心设计的问卷工具,系统性地收集受访者在态度、行为、特征等方面的信息。成功的问卷调查关键在于:第一,问卷设计需遵循明确性、中立性和完备性原则,避免引导性问题和歧义表述;第二,采用科学的抽样方法(如分层抽样、整群抽样)确保样本的代表性;第三,通过预调查测试信度和效度。
现代问卷调查已从传统纸质形式发展为以在线平台为主的数字化模式。线下调查具有回答质量高、样本控制精准、适用于特殊群体等优势,但存在成本高昂、时间周期长、地理限制明显等劣势。线上调查则展现出成本效益高、执行效率快、无地理限制等优点,但也面临样本代表性偏差、回答质量难以控制等技术性挑战。在实际研究实践中,越来越多的调查采用混合模式,结合线上和线下优势,根据不同样本特征选择最合适的调查方式,或者在同一个研究中使用多种方式互补,以期获得最具代表性的高质量数据。
实验方法:在可控制的环境中,操纵一个或多个自变量,观察其对因变量的影响,从而探索因果关系。实验设计的效力源于三方面:1.随机化分配以平衡各组间的未知混杂因素;2.设置对照组以提供比较基准;3.实施盲法(单盲或双盲)以消除主试和被试的期望效应。根据实验环境的不同,可分为两类:实验室实验,即在专门设计的、高度受控的环境中进行,能最大限度地排除干扰。因此,其内部效度很高,但人工控制的实验环境可能会在一定程度上削弱结论在真实世界中的普适性(即外部效度)。与之相对的是田野实验,它在真实的自然场景(如学校、社区、市场)中实施,外部效度高,结论更具现实意义,但相应地,对无关变量的控制难度也显著增大。
近年来,实验方法经历了重要的数字化革新。线上实验平台的兴起,为研究者提供了前所未有的便利。这些平台能够以较低的成本和极快的速度招募大量、多样化的被试,极大地促进了大样本、多因素行为实验的开展。然而,线上实验也面临新的挑战,如对实验过程监控的弱化、被试注意力难以保证以及可能存在重复参与等数据质量问题。在实际研究中,选择何种实验方法取决于研究问题的性质。探索精确机制和因果链条的基础研究,往往倾向于选择控制严格的实验室实验;而旨在检验政策或干预措施实际效果的评估研究,则更青睐外部效度更高的田野实验。同时,混合实验设计也日益常见,例如先在实验室中初步验证一个理论假设,再通过田野实验检验其在真实情境下的稳健性,从而实现内部效度与外部效度的平衡。
观察方法:通过系统性地观看、倾听并记录研究对象在自然或设定情境下的行为、互动及现象,以获取研究数据。它适用于研究对象无法进行有效自我报告或当研究焦点是潜意识行为、社会互动过程,而问卷或实验方法可能引发“观察者效应”(即被试因被研究而改变行为)的情况。根据研究者介入程度的不同,观察方法主要分为两类:参与式观察,它要求研究者深入研究对象所在的群体或环境(如社区、组织),在一段时间内作为成员进行生活和工作,从内部视角理解群体的文化、规范和行为模式。其优势在于能获得深刻、情境化的“内部人”知识,但挑战在于如何保持客观并避免过度卷入。非参与式观察,则要求研究者作为纯粹的旁观者,不介入任何活动,仅客观记录所见所闻,这种方式能最大程度保证观察的客观性,但可能难以洞察行为背后的深层动机和文化语境。
观察方法的核心优势在于其高外部效度,能够捕捉到真实、自然发生的行为,这是实验室环境或自我报告数据难以企及的。然而,其也有一些固有的局限性:易受观察者偏差(研究者的期望、价值观无意中影响记录和解读)的影响;数据记录和后续分析的耗时性;以及观察结果在可重复性和普适性方面的挑战。在实际研究中,观察方法常与其他方法结合使用,形成方法三角互证。例如,通过观察发现某种现象,再通过问卷调查其普遍性,或通过深度访谈探寻其背后的原因。这种混合方法策略能有效弥补单一方法的不足,使研究结论更全面、更坚实。
二手数据是指由其他个人或机构出于非当前研究目的而事先收集、整理并储存的各类数据资源。其特征在于,数据的产生并非服务于研究者当下的具体课题,对这些资料的运用本质上属于一种“二次开发”。因此,在使用过程中需对其适用性、准确性与时效性保持审慎的评估态度。
二手数据来源广泛、形态多样:有权威性较高的政府及统计机构发布的数据,例如国家与地方统计局出版的统计年鉴、各类普查数据以及各部委公开的行业统计资料等;有科研价值较高的来自学术研究机构共享的数据资源,如中国家庭追踪调查、科学数据平台等。此外,还有一类来源较为广泛且实用的数据,包括商业机构发布的行业分析报告、各级政府建立的公共数据开放平台所提供的政务信息,以及通过合规手段从互联网中获取的公开资讯等。
在数字时代背景下,我们还需要关注一类特殊的二手数据——“机器生成的大数据”。这类数据源自传感器、智能设备和互联网平台的自动化运作,其初始目的在于支持企业运营、社会运转,而非直接服务于学术研究。它在具备二手数据基本属性的同时,还表现出体量巨大、生成迅速、形式多样和真实客观等典型特征。这类数据为洞察宏观社会模式与人类行为规律提供了全新视角,但同时也对研究者的数据处理能力与研究伦理提出了更高要求。
二手数据的主要优势体现在其经济性与便利性方面。它可帮助研究者大幅节省时间与资金成本,迅速获取大规模样本,为探索性分析与背景研究提供可能。然而,其局限性亦不可忽视:由于原始数据的收集目的与研究需求可能存在偏差,其适用性常受到质疑;研究者对数据质量的控制力较弱,在准确性与时效性方面需格外谨慎验证;尤其在运用商业大数据时,还需要确保数据来源与使用方式的合法合规。
无论是直接调查数据还是二手数据,我们的第一步都是进行数据清洗(Data Cleaning)。数据清洗的目标是将原始的、杂乱的数据处理成整洁数据(Tidy Data),以满足后续分析和建模的要求。它涉及数据诊断、问题处理和质量验证。下面我将基于R语言的tidyverse系列包阐述一个典型的数据清洗流程。tidyverse是一个专为数据科学设计的R包集合,它提供了一套连贯、人性化且强大的函数。其核心理念是整洁数据原则(Tidy Data Principles),即规范化数据应满足:
每个变量独占一列(Each variable forms a column)
每条观测独占一行(Each observation forms a row)
每种观测单元构成独立表格(Each type of observational unit forms a table)
例3-1:
我们创造一个虚拟数据集来说明R语言数据清洗的过程。假设我们获得了一个数据集,记录了2030年亚太地区12起重大自然灾害事件,包括洪灾、地震、台风等,数据由各国政府及国际组织上报,但因上报标准不统一,存在多语言混杂、单位不一致、异常值和缺失值等问题。我们需要对数据进行清洗。
1.首先,并进行初步诊断。我们导入必要的R工具包,包括用于数据处理的tidyverse套件、专门处理日期时间数据的lubridate包,以及读取Excel文件的readxl包。这些工具为后续的数据清洗提供基础支持。
数据读入后,通过两个步骤进行初步诊断:首先使用glimpse()函数查看数据的基本结构,包括行列数量、各列数据类型和前几个观测值,以发现名称、数据类型等问题;其次使用summary()函数生成统计摘要,了解数值变量的分布特征和缺失值情况。
例3-1-1:
# 加载必要的R包
library(tidyverse)
library(lubridate)
library(readxl)
#从Excel文件导入原始数据
disaster_raw <- read_excel("disaster_messy_data.xlsx")
# 查看数据结构
glimpse(disaster_raw)
## Rows: 21
## Columns: 7
## $ 事件ID <chr> "DIS-2030-1001", "DIS-2030-1002", "DIS-2…
## $ `Event Date` <chr> "2030-01-17", "19/03/2030", "20300707", …
## $ `Disaster Type` <chr> "Flood", "flooding", "洪水", "Earthquake",…
## $ Casualties <dbl> 91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69,…
## $ `Economic Loss (million USD)` <dbl> 5800.0, 740.0, 20.0, 15000.0, 255.0, 120…
## $ `Coastal Area` <chr> "N", "Y", "Y", "N", "Y", "Y", "Y", NA, "…
## $ `Severity Level` <chr> "severe", "moderate", "mild", "severe", …
# 统计摘要
summary(disaster_raw)
## 事件ID Event Date Disaster Type Casualties
## Length:21 Length:21 Length:21 Min. : 0
## Class :character Class :character Class :character 1st Qu.: 5
## Mode :character Mode :character Mode :character Median : 12
## Mean : 4820
## 3rd Qu.: 69
## Max. :99999
##
## Economic Loss (million USD) Coastal Area Severity Level
## Min. : -100.0 Length:21 Length:21
## 1st Qu.: 204.2 Class :character Class :character
## Median : 770.0 Mode :character Mode :character
## Mean : 2370.8
## 3rd Qu.: 1870.0
## Max. :15000.0
## NA's :1
我们可以采用分层处理策略:首先手动处理特殊列名,将中文列名转换为有意义的英文标识符,并去除包含单位信息的冗余部分;随后使用自动化方法批量处理剩余列名,将所有空格替换为下划线,并将所有字符转换为小写,最终实现snake_case命名规范。这种命名规范(如”event_date”)相比驼峰命名法(“eventDate”)或帕斯卡命名法(“EventDate”)在数据处理场景中具有更好的可读性和兼容性,是tidyverse生态系统推荐的命名法。
例3-1-2:
#标准化列名
# 使用管道操作符 %>% 将多个清洗步骤连接起来,提高代码可读性
disaster_clean <- disaster_raw %>%
# 对无规则列名手动调整
rename(
# 将中文列名改为英文
event_id = `事件ID`,
# 将包含空格和括号的长列名改为简洁的英文名
economic_loss = `Economic Loss (million USD)`,
) %>%
# 应用rename_with() 函数:对有规则的列应用相同的转换规则
rename_with(
# 匿名函数:定义列名转换规则
# ~ 表示匿名函数开始
# . 表示当前的列名(占位符)
~ str_to_lower( # 1. 将所有字母转为小写
str_replace_all(., " ", "_") # 2. 先将空格替换为下划线
)
# 例如 "Event Date" → "event_date"
)
glimpse(disaster_clean)
## Rows: 21
## Columns: 7
## $ event_id <chr> "DIS-2030-1001", "DIS-2030-1002", "DIS-2030-1003", "DIS…
## $ event_date <chr> "2030-01-17", "19/03/2030", "20300707", "2030-08-07", "…
## $ disaster_type <chr> "Flood", "flooding", "洪水", "Earthquake", "地震", "TYPHOON…
## $ casualties <dbl> 91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69, 12, 6, 99999, …
## $ economic_loss <dbl> 5800.0, 740.0, 20.0, 15000.0, 255.0, 12000.0, 430.0, 25…
## $ coastal_area <chr> "N", "Y", "Y", "N", "Y", "Y", "Y", NA, "N", "N", NA, "N…
## $ severity_level <chr> "severe", "moderate", "mild", "severe", "moderate", "se…
为确保数据的完整性和一致性,采用两步处理策略:第一步,类型统一化。首先,将各种格式的日期字符串统一解析为R语言的Date类型。采用分层解析策略:优先尝试最常见的ISO 8601标准格式(ymd函数),如果失败则尝试欧洲格式(dmy函数),最后使用parse_date_time函数的灵活解析能力处理复杂或混合格式的情况。coalesce函数确保选择第一个成功解析的结果,实现格式的自动识别与转换。第二步,格式标准化。所有日期成功解析为Date类型后,使用format函数将其统一转换为标准的YYYY-MM-DD字符串格式。这一步骤确保导出数据和后续分析中日期格式的完全一致性。
例3-1-3:
disaster_clean <- disaster_clean %>%
mutate(
# 用mutate修改现有列
event_date = coalesce(
parse_date_time(event_date, orders = c("ymd", "dmy", "mdy"))
)
) %>%
# 确保所有日期都显示为国际标准格式YYYY-MM-DD格式
mutate(
event_date = format(event_date, "%Y-%m-%d") # 格式化为标准字符串
)
glimpse(disaster_clean)
## Rows: 21
## Columns: 7
## $ event_id <chr> "DIS-2030-1001", "DIS-2030-1002", "DIS-2030-1003", "DIS…
## $ event_date <chr> "2030-01-17", "2030-03-19", "2030-07-07", "2030-08-07",…
## $ disaster_type <chr> "Flood", "flooding", "洪水", "Earthquake", "地震", "TYPHOON…
## $ casualties <dbl> 91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69, 12, 6, 99999, …
## $ economic_loss <dbl> 5800.0, 740.0, 20.0, 15000.0, 255.0, 12000.0, 430.0, 25…
## $ coastal_area <chr> "N", "Y", "Y", "N", "Y", "Y", "Y", NA, "N", "N", NA, "N…
## $ severity_level <chr> "severe", "moderate", "mild", "severe", "moderate", "se…
例3-1-4:
disaster_clean <- disaster_clean %>%
mutate(
disaster_type = case_when(
str_detect(disaster_type, "Flood|洪水|flooding") ~ "flood",
str_detect(disaster_type, "Earthquake|地震") ~ "earthquake",
str_detect(disaster_type, "cyclone|Cyclone|飓风") ~ "typhoon",
str_detect(disaster_type, "wildfire|野火") ~ "wild fire",
str_detect(disaster_type, "干旱") ~ "drought",
TRUE ~ tolower(disaster_type)
) %>% factor(),
# 有序因子转换
severity_level = ordered(severity_level, levels = c("mild", "moderate", "severe"))
)
glimpse(disaster_clean)
## Rows: 21
## Columns: 7
## $ event_id <chr> "DIS-2030-1001", "DIS-2030-1002", "DIS-2030-1003", "DIS…
## $ event_date <chr> "2030-01-17", "2030-03-19", "2030-07-07", "2030-08-07",…
## $ disaster_type <fct> flood, flood, flood, earthquake, earthquake, typhoon, f…
## $ casualties <dbl> 91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69, 12, 6, 99999, …
## $ economic_loss <dbl> 5800.0, 740.0, 20.0, 15000.0, 255.0, 12000.0, 430.0, 25…
## $ coastal_area <chr> "N", "Y", "Y", "N", "Y", "Y", "Y", NA, "N", "N", NA, "N…
## $ severity_level <ord> severe, moderate, mild, severe, moderate, severe, moder…
例3-1-5:
disaster_clean <- disaster_clean %>%
mutate(
# 基于领域知识的清洗
casualties = replace(casualties, casualties > 1e4, NA),
economic_loss = replace(economic_loss, economic_loss < 0, NA),
)
glimpse(disaster_clean)
## Rows: 21
## Columns: 7
## $ event_id <chr> "DIS-2030-1001", "DIS-2030-1002", "DIS-2030-1003", "DIS…
## $ event_date <chr> "2030-01-17", "2030-03-19", "2030-07-07", "2030-08-07",…
## $ disaster_type <fct> flood, flood, flood, earthquake, earthquake, typhoon, f…
## $ casualties <dbl> 91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69, 12, 6, NA, 8, …
## $ economic_loss <dbl> 5800.0, 740.0, 20.0, 15000.0, 255.0, 12000.0, 430.0, 25…
## $ coastal_area <chr> "N", "Y", "Y", "N", "Y", "Y", "Y", NA, "N", "N", NA, "N…
## $ severity_level <ord> severe, moderate, mild, severe, moderate, severe, moder…
例3-1-6:
map_dbl(disaster_clean, ~mean(is.na(.))) %>% # 对每列计算缺失值比例
tibble(variable = names(.), na_rate = .) # 转换为数据框格式
## # A tibble: 7 × 2
## variable na_rate
## <chr> <dbl>
## 1 event_id 0
## 2 event_date 0
## 3 disaster_type 0.0476
## 4 casualties 0.0476
## 5 economic_loss 0.0952
## 6 coastal_area 0.143
## 7 severity_level 0
# 为了方便后面举例,我们将NA值补全(假设我们经过反复查证)
disaster_clean$disaster_type[which(is.na(disaster_clean$disaster_type))] <-c("flood")
disaster_clean$casualties[which(is.na(disaster_clean$casualties))] <-c(9)
disaster_clean$economic_loss[which(is.na(disaster_clean$economic_loss))] <-c(500,100)
disaster_clean$coastal_area[which(is.na(disaster_clean$coastal_area))] <-c("Y")
例3-1-7:
library(writexl)
write_xlsx(disaster_clean, "disaster_data_cleaned.xlsx") # 导出清洗后的数据为Excel文件
例3-1-8:
数据生产代码
# 设置随机种子确保可重复性
set.seed(2030)
# 定义日期序列随机产生的辅助变量
datedata <- seq(ymd("2030-01-01"), ymd("2030-12-31"), by = "day") %>% as.character()
# 用tibble函数创建数据集
disaster_messy_data <- tibble(
# 1. 事件ID(列名)
`事件ID` = c(paste0("DIS-2030-", 1001:1021)),
# 2. 日期列(多种格式,含缺失)。
`Event Date` = c(
"2030-01-17", "19/03/2030", "20300707", "2030-08-07", "2030/06/18",
sample(datedata, 16, replace = FALSE)
),
# 3. 灾害类型(不统一:中英文混合、大小写不一、含缺失)
`Disaster Type` = c("Flood", "flooding", "洪水", "Earthquake", "地震", "TYPHOON",
"洪水", "earthquake", "地震", "Flood", "flood", "飓风",
"Typhoon", "洪水", "Cyclone", "typhoon", "飓风",
"干旱", "Earthquake", "earthquake", NA),
# 4. 伤亡人数(含异常值和缺失值)
`Casualties` = c(
91, 28, 3, 699, 6, 77, 6, 20, 24, 4, 69, 12, 6, 99999, 8, 3, 0, 126, 27, 2, 5
),
# 5. 经济损失(含缺失值,单位为百万美元)
`Economic Loss (million USD)` = c(5800, 740, 20, 15000, 255, 12000, 430, 2530,
1650, 250, 4120, 245, 800, NA, 32.2, 41, 970, 1250, 1300, 82, -100),
# 6. 是否沿海地区
`Coastal Area` = sample(c("Y", "N", NA), 21, replace = TRUE, prob = c(0.4, 0.5, 0.1)),
# 7. 灾害严重程度(未能表明等级关系)
`Severity Level` = c("severe", "moderate", "mild", "severe", "moderate", "severe",
"moderate", "severe", "severe", "mild", "severe", "moderate",
"moderate", "mild", "mild", "mild", "moderate", "severe",
"moderate", "mild", "mild"
)
)
# 保存为Excel文件
write_xlsx(disaster_messy_data, "disaster_messy_data.xlsx")
数据本身是沉默的,它无法主动诉说自己的意义。数据的表达,就是让这些沉默意义通过精心设计的方式呈现出来。这种呈现,不止是技术,也是艺术,是科学的客观与个人认知和社会认知的主观的结合。无论是有意识还是无意识,我们表达数据主要基于以下四个核心目的:
原始数据集通常规模庞大、结构复杂,充满了细节。人类认知系统难以直接从海量原始数据中快速捕捉到关键模式和核心结论。通过图表、摘要统计量或表格等形式对数据进行表达,可以过滤噪音、提炼精华,将复杂的数据结构简化为核心信息,从而高效、准确地将研究发现传递给读者或决策者。例如,面对一份包含数千条灾害记录的数据,一张“不同灾害类型年均经济损失”的柱状图,一句“近十年灾害经济损失呈下降趋势”所传递的信息,远比直接浏览原始数据表要直观和高效得多。
数据的图形化表达(如散点图、折线图、热力图)是探索性数据分析的核心工具。许多隐藏的模式、趋势、异常点和关联关系在原始数据中难以察觉,但通过图形化表达却可以一目了然。例如,通过绘制“灾害发生时间序列图”,我们可以轻松发现某些灾害是否具有季节性规律;通过“震级与伤亡人数散点图”,可以快速判断两者之间是否存在相关性。表达的过程本身就是一种强大的分析工具。
在公共管理等领域,数据驱动的决策至关重要。清晰的数据表达能够将抽象的数字转化为具象的洞察,为决策者提供坚实的证据基础,从而制定更科学、更有效的策略。例如,一份向应急管理部门提交的报告,如果能用地图清晰展示“洪涝灾害高风险区域”,并用显示屏显示“救援资源实时分配情况”,将能极大地帮助指挥者优化资源布局,提升应急响应效率。
数据表达是讲述数据故事的核心。通过有逻辑地组织文字和图表,研究者可以构建一个引人入胜、具有说服力的叙事,引导受众理解问题的背景、分析的过程和最终的结论。一个优秀的表达能够激发共鸣、建立共识并推动改变。相比之下,单纯罗列文字和数字往往显得枯燥且缺乏感染力。去和领导分别说一下这两句话,试一下效果:“公司最近业绩不太好”,“最近业绩数据显示,公司营收下降了25%。”。
总之,数据表达,是一种表达,它的目标是把“数据中的事实”传递给“受众”。我们的课本只能在技术上告诉大家一些普遍情况。但请记住,选择正确的表达方式,永远取决于你的数据、你的信息以及你的受众。特别是你的受众。
文字表达的重要性在社会科学领域不言而喻。文字表达的意义在于,通过精准的语言描述和数据引用,将分析结果转化为逻辑清晰、重点突出的叙述,引导读者理解数据背后的含义。与写文章一样,尽管数据的文字表达会有很多种风格,但有一些核心的原则可以用作普遍参考:
它通常遵循”总-分”的原则:先用概括性语言点明核心发现,再用具体数据支撑论点。比如在分析灾害数据时,可以这样组织文字:“从灾害类型分布来看,气象类灾害占据主导地位。在全年记录的52起灾害事件中,洪水与台风合计占比达到65%,共造成34起事件。”这种结构既避免了单纯罗列数据的枯燥,又确保了每个观点都有扎实的数据支撑。
过于专业的术语会制造理解障碍,而过度简化又可能损失信息的准确性。例如,在描述相关性时,“降雨量与灾害损失呈现正相关”是专业表述,而”降雨量越大,灾害损失往往越严重”则更通俗易懂。优秀的作者懂得根据受众背景调整表达方式,既保持专业严谨,又确保传达效果。
当我们在报告中写道”本季度销售额环比增长15%“,这不仅仅是一个数字的陈述,而是在建立比较关系(环比)、指明变化方向(增长)和量化变化幅度(15%)。这种表达将孤立的数据点嵌入到特定的语境中,使其获得意义。例如,仅仅列出”洪水灾害损失1500万元”是一个信息片段,但如果说”本次洪水造成的直接经济损失达到1500万元,相当于该地区年度财政预算的12%”,数据就立即获得了参照系和重要性。
图表可以展示趋势的突变,但只有文字能够解释突变的原因。比如:“尽管本季度灾害数量下降20%,但单次灾害平均损失上升35%,主要原因是遭遇了十年一遇的特大洪水。“这样的说明不仅描述了”是什么”,更解释了”为什么”,为决策提供了更深层的依据。
值得注意的是,数据的文字表达正在与自然语言生成技术深度融合。很多智能系统已经能够自动将简单的数据模式转化为文字描述,如”销售额在连续三个月下降后,本月出现回升迹象”。但真正有洞察力的解读,那些需要结合领域知识、理解业务背景的深度分析,仍然需要人类的智慧。
表格在数据分析中具有双重属性:它既是存储数据的容器,又是表达数据的工具。作为容器,表格以行列结构承载原始数据;作为表达工具,表格通过精心设计呈现关键信息。与文字和图表相比,表格具有以下显著优势:它可以同时呈现多个维度的数据关系,展示需要精确引用的数值,进行跨行跨列的系统性比较,以有限空间内表现大量数据。
1 . 作为数据容器的表格:
作为数据存储的容器,表格的设计直接决定了数据质量、处理效率和后续分析的可能性。一个结构良好的表格应遵循逻辑优先原则,通常将唯一标识列(如ID、日期)置于前部,作为数据索引;后面根据研究关注,排列分类变量和连续变量,便于统计计算与分析。在R语言的数据科学实践中,建议以长格式(long format )作为主要的存储形式,因为它符合“一行一观测、一列一变量”的整洁数据原则,便于进行分组聚合、统计建模和可视化分析,也更容易转换为宽格式用于报表展示。在数据类型存储方面,应兼顾精度与效率。日期时间建议采用Date或POSIXct类型,以便确保排序正确和计算便捷;分类变量应使用因子(factor)编码,以优化内存占用和分组速度;布尔值采用logical类型便于直接进行逻辑运算;文本数据建议存储为character类型,注意控制长度;货币金额可考虑以整数形式或精确小数存储,以避免浮点计算误差。完整的表格系统还应整合元数据管理,通过建立独立的数据字典,记录变量名、中文标签、数据类型、取值范围、说明及计算方式等关键信息。这种“自描述”设计不仅保障了数据的可追溯性,也为从原始数据到分析洞察的全过程构建了可验证的流程支撑。我们用为前面清洗好的数据,做一份数据字典。
例3-1-9:
disaster_clean_note <- tibble(
变量名 = names(disaster_clean),
中文标签 = c("事件ID", "事件日期", "灾害类型", "伤亡人数", "经济损失(百万美元)",
"是否沿海地区", "灾害严重程度"),
数据类型 = c("字符型", "字符型", "因子型", "数值型", "数值型",
"字符型", "有序因子型"),
取值范围 = c("DIS-2030-XXXX", "2030-01-01至2030-12-31",
"flood/earthquake/typhoon/drought", "0-10000",
">=0", "Y/N", "mild/moderate/severe"),
数据来源 = c("系统生成", "事件报告", "分类系统", "伤亡统计", "经济损失评估",
"地理信息系统", "灾害评估"),
完整性 = paste0(round(100 - sapply(disaster_clean, function(x) sum(is.na(x))/length(x)*100), 1), "%")
)
library(knitr)
kable(disaster_clean_note,
caption = "清洗后灾害数据的数据字典",
align = c("l", "l", "c", "l", "l", "c"),
format = "simple")
| 变量名 | 中文标签 | 数据类型 | 取值范围 | 数据来源 | 完整性 |
|---|---|---|---|---|---|
| event_id | 事件ID | 字符型 | DIS-2030-XXXX | 系统生成 | 100% |
| event_date | 事件日期 | 字符型 | 2030-01-01至2030-12-31 | 事件报告 | 100% |
| disaster_type | 灾害类型 | 因子型 | flood/earthquake/typhoon/drought | 分类系统 | 100% |
| casualties | 伤亡人数 | 数值型 | 0-10000 | 伤亡统计 | 100% |
| economic_loss | 经济损失(百万美元) | 数值型 | >=0 | 经济损失评估 | 100% |
| coastal_area | 是否沿海地区 | 字符型 | Y/N | 地理信息系统 | 100% |
| severity_level | 灾害严重程度 | 有序因子型 | mild/moderate/severe | 灾害评估 | 100% |
当我们使用表格来表达数据时,需要从信息接收者的角度思考如何让表格更有效地传达信息。首先,我们需要明确表格主题与目的。每个表格都应有清晰的主题,服务于特定的分析目的。避免将过多无关信息堆砌在同一表格中,确保读者能快速理解表格要表达的核心观点。其次,表格的逻辑结构应符合自然的阅读顺序:从左到右、从上到下。通常将分类变量置于左侧,汇总指标置于右侧;重要维度放在前面,细节信息放在后面。再次,表格应在信息丰富度与可读性间取得平衡。过少的行或列可能遗漏重要信息,过多的行或列则会让读者迷失重点。通常建议表格行数不超过屏幕一页显示范围。同时,表格应便于读者进行比较。通过合理的排序(如按重要性、数值大小)、分组(如按类别、时间)和格式强调(如加粗、颜色),引导读者关注关键差异和模式。最后,表格还需要包含必要的解释信息:清晰的标题、有意义的列名、明确的单位标注、数据来源和时间范围说明。
根据这些表达原则,我们用清理好的disaster_clean数据,制作一份用于简报的灾害影响摘要表:
例3-1-10:
# 目的:展示2023年各灾害类型的主要影响指标
simple_summary <- disaster_clean %>%
group_by(disaster_type) %>%
summarise(
# 选择关键指标,便于对比
事件次数 = n(),
伤亡人数 = sum(casualties, na.rm = TRUE),
经济损失_百万美元 = round(sum(economic_loss, na.rm = TRUE), 1),
) %>%
# 按重要性排序,从左到右依次展示
arrange(desc(伤亡人数)) %>%
# 5列×灾害类型数,密度适中
mutate(
伤亡人数 = format(伤亡人数, big.mark = ","),
经济损失_百万美元 = format(经济损失_百万美元, big.mark = ","),
)
# 使用knitr的kable函数获得更好看的表格
library(knitr)
kable(simple_summary,
col.names = c("灾害类型", "事件次数", "伤亡人数", "经济损失(百万美元)"),
caption = "2030年灾害事件影响分析",
align = c("l", "r", "r", "r"),
format = "simple")
| 灾害类型 | 事件次数 | 伤亡人数 | 经济损失(百万美元) |
|---|---|---|---|
| earthquake | 6 | 778 | 20,817.0 |
| flood | 8 | 215 | 11,960.0 |
| drought | 1 | 126 | 1,250.0 |
| typhoon | 6 | 106 | 14,088.2 |
接下来,是最重要的数据的图形表达了。数据的图形表达,也可以分为两大类:基于原始数据的图形表达与基于数据特征的图形表达。
1.基于原始数据的图形表达
此类图形直接在原始观测值的基础上构建,尽可能保留并展示数据的原始分布形态与细节信息,适用于数据量较小、需要观察每个数据点具体情况的分析场景。
a).茎叶图 (Stem-and-Leaf Plot):一种同时展示数据分布形态和保留原始数值的图表。它将每个数据点分成“茎”(通常为首位或前几位数字)和“叶”(通常为末位数字)两部分,沿一条基准线排列,形似茎叶。该图既能直观显示数据的集中趋势和离散程度,又能还原部分原始数据信息,适用于小型数据集的初步分析。
例3-2:
我们利用30位同学的考试成绩做茎叶图。从图中,我们一方面能看到成绩呈现65-70分和70-80 分两个主要集中区域的分布趋势,另一方面还可以还原出原始成绩的具体数值。比如,50分数段的有两位同学,成绩分别是55和58,90分数段的有三位同学,成绩分别是91、92和93。
set.seed(123)
# 生成 30 个服从正态分布的成绩,并取整
exam_scores <- round(rnorm(30, mean = 75, sd = 10))
# 限制一下范围在 0-100 之间
exam_scores <- pmax(0, pmin(100, exam_scores))
# 绘制茎叶图
stem(exam_scores)
##
## The decimal point is 1 digit(s) to the right of the |
##
## 5 | 58
## 6 | 244
## 6 | 588999
## 7 | 0133
## 7 | 666799
## 8 | 0023
## 8 | 78
## 9 | 123
b).点图 (Dot Plot) / 云图 (Strip Chart):将每个数据点以单个点的形式绘制在一条或多条数值轴上,用于显示一维数据的实际分布位置。当数据点有重叠时,可通过轻微抖动(jittering)避免重叠,从而更清晰地展示点的密集程度。该图非常适用于比较几个类别或条件下的原始数据分布。
例3-3:
我们利用 200 位城市白领的每日“通勤时间”做点图。从图中我们能清晰地看到数据分布的形态:大部分数据点集中在 30-50 分钟区间,像一条厚实的云带,代表了主流群体的通勤时长;在图的右侧尾部,稀疏地延伸出几个长条形的离散点,直观地揭示了“极端通勤族”的存在(如 120 分钟以上)。
# 模拟200位白领的通勤时间
set.seed(2026)
commute_time <- round(rlnorm(200, meanlog = 3.6, sdlog = 0.5))
# 绘制点图/云图
stripchart(commute_time,
method = "jitter", # 对连续数据,jitter能增加云带的厚度感
pch = 16, # 实心圆
col = rgb(0,0,0,0.4), # 半透明绿色
xlab = "每日通勤时间 (分钟)",
main = "城市白领通勤时间分布云图")
# 添加网格线
grid(nx = 20, col = "gray", lty = "dotted")
c). 散点图 (Scatter Plot):在直角坐标系中,使用点的位置表示两个连续变量之间的观测值。每个点对应一个观测单位,其横纵坐标分别代表两个变量的取值。散点图主要用于直观地考察两个定量变量之间的相关关系、聚类情况以及是否存在异常值。
例3-4:
我们调查了30名员工的工作年限与月薪。通过散点图,可以直观地看到一种正相关趋势:随着工作年限(X轴)的增加,员工的月薪(Y轴)通常也会上升。虽然图中每个点都是独立的,但整体分布呈现从左下角向右上角延伸的形态,这反映了工作经验积累对薪资提升的普遍影响。同时,我们也可以观察到图中有一个异常值(3,12),我们需要对改点进一步核实其准确性。
library(ggplot2)
library(dplyr)
set.seed(2026)
# 工作年限
work_years <- 1:15
x_data <- rep(work_years, 2)
# 模拟薪资
y_data <- x_data * 1 + runif(30, min = -3, max = 3)
# 创造一个异常数据
y_data[3] <- 12
# 组合数据
disaster_clean <- data.frame(
work_years = x_data,
salary = y_data
)
# 使用 %>% 风格绘制散点图
disaster_clean %>%
ggplot(aes(x = work_years, y = salary)) +
geom_point(size = 3, alpha = 0.7, color = "grey30") +
labs(x = "工作年限 (年)",
y = "月薪 (千元)",
title = "工作年限与月薪的关系") +
theme_minimal()
例3-5:
通过观察某公司某年全年的销售额线图,可以清晰地看到销售业绩随时间推移的连续变化轨迹。折线的整体走向直观地展示了业务显著的增长趋势,而线条的局部起伏则准确捕捉了不同月份的市场波动情况。这种线图让我们能迅速识别出销售趋势和关键拐点,从而更好地把握数据随时间流动的动态脉络。
library(ggplot2)
library(dplyr)
# 模拟生成某公司某年 1-12 月的月度销售额数据
set.seed(2026)
line_data <- tibble(
month = 1:12,
# 生成带有增长趋势和随机波动的数据
sales = 200 + seq(0, 50, length.out = 12) + rnorm(12, 0, 5)
)
#绘图-
ggplot(line_data, aes(x = month, y = sales)) +
# 使用 geom_line 绘制折线
geom_line(size = 1, color = "#2c7bb6") +
# 使用 geom_point 标记具体数据点
geom_point(size = 3, color = "#2c7bb6") +
# 简写:直接设置刻度位置
scale_x_continuous(breaks = 1:12) +
labs(title = "某年某公司月度销售额变化趋势",
x = "月份",
y = "销售额 (万元)") +
theme_minimal() +
theme(
plot.title = element_text(face = "bold")
)
此类图形并非直接绘制原始数据点,而是先对数据进行概括性统计(如汇总、分组、计算统计量),然后利用这些统计特征或汇总结果来构建图形。它适用于概括数据分布的主要特征,尤其利于进行组间比较或展示整体构成。
a). 饼图 (Pie Chart):一种圆形统计图,通过扇形的面积(或中心角)大小来表示一个分类变量各类别的频数或比例构成。它强调部分与整体之间的关系,适用于展示一个分类变量的构成比例,但不利于精确比较不同类别间的大小或比较多个数据集。
例3-6:
在一项城市青年生活方式的调查中,我们分析了400名受访者的学历构成。通过饼图,可以直观地看到“本科”学历的受访者占据了样本的绝对主导地位(约 50%),“硕士”学历紧随其后,而“高中及以下”和“大专”占比相对较小但比例相当。这种视觉化的分布迅速揭示了该调查群体以接受过高等教育的群体为主,有助于后续分析不同教育背景下的生活方式差异。
library(ggplot2)
library(dplyr)
set.seed(2026)
# 模拟受访者学历数据
raw_edu_data <- tibble(
education = sample(c("本科", "硕士", "高中及以下", "大专"),
size = 400,
replace = TRUE,
prob = c(0.5, 0.25, 0.125, 0.125))
)
# 计算各学历类别的频数和百分比
plot_edu_data <- raw_edu_data %>%
count(education) %>%
mutate(percentage = n / sum(n))
# 绘图过程
ggplot(plot_edu_data, aes(x = "", y = percentage, fill = education)) +
geom_col(width = 1, color = "white") +
coord_polar("y") +
scale_fill_brewer(palette = "Spectral") +
labs(title = "受访者学历构成调查",
fill = "学历层次") +
theme_void()
b).条形图 (Bar Chart / Bar Plot):使用一系列高度(或长度)与频数、频率或其他统计量成比例的矩形条来表示分类变量各水平的取值大小。可分为频数条形图、频率条形图等,主要用于比较不同类别的统计量,或展示分类变量的分布特征。当矩形条水平放置时,也称为棒棒图。
例3-7:
我们对比了五个不同街道社区的平均公共图书借阅量。通过条形图,可以清晰地看到“A街道”的借阅量显著高于其他街道,而“E街道”的借阅量最低。这种阶梯状的对比表明,不同社区之间的公共阅读活跃度存在明显的差异,社区 A 可能拥有更好的阅读推广政策或更便利的借阅设施,而社区 E 则可能需要资源扶持以提升居民的阅读积极性。
library(ggplot2)
library(dplyr)
set.seed(2026)
# 构建原始借阅记录(每条记录代表一次借阅,包含随机波动)
raw_community_data <- tibble(
district = rep(c("A街道", "B街道", "C街道", "D街道", "E街道"), each = 50),
borrowed = c(
rnorm(50, mean = 500, sd = 20), # A 街道基础数据
rnorm(50, mean = 450, sd = 20), # B 街道基础数据
rnorm(50, mean = 400, sd = 20), # C 街道基础数据
rnorm(50, mean = 350, sd = 20), # D 街道基础数据
rnorm(50, mean = 300, sd = 20) # E 街道基础数据
)
)
# 按街道分组计算均值
plot_community_data <- raw_community_data %>%
group_by(district) %>%
summarise(avg_borrowed = mean(borrowed)) %>% # 提取平均数特征
ungroup() %>%
mutate(district = factor(district, levels = district)) # 保持数据顺序
## --- 2. 绘图过程 ---
ggplot(plot_community_data, aes(x = district, y = avg_borrowed, fill = district)) +
geom_col(alpha = 0.8, width = 0.6) +
scale_fill_brewer(palette = "Blues") +
labs(title = "各街道社区平均图书借阅量",
x = "社区街道",
y = "平均借阅量 (本)",
fill = "街道") +
theme_minimal() +
theme(legend.position = "none")
c).分组条形图 (Grouped Bar Chart):条形图的一种变体,用于同时展示两个分类变量之间的关系。它将一个分类变量的各个水平作为主分组,在每个主分组内再按另一个分类变量的水平进行细分并并列显示。适用于比较两个分类变量交叉组合下的频数或统计量,能够直观显示变量间的交互作用。
例3-8:
我们考察了“本地户籍”与“非本地户籍”人群对某项积分落户政策的满意度评价。首先,我们对调查问卷数据进行了交叉频数分析(Crosstabulation),统计了在不同户籍类型下选择“满意”或“不满意”的具体人数。通过可视化这些统计结果,可以发现明显的态度分化:统计数据显示,“本地户籍”群体中表示“满意”的人数远多于“不满意”;而“非本地户籍”群体则呈现相反态势。
library(ggplot2)
library(dplyr)
set.seed(2026)
# 构建原始调查微观数据
raw_policy_data <- tibble(
# 生成 100 个本地人,80 个满意,20 个不满意
type = c(rep("本地户籍", 80), rep("本地户籍", 20),
# 生成 100 个外地人,30 个满意,70 个不满意
rep("非本地户籍", 30), rep("非本地户籍", 70)),
attitude = c(rep("满意", 80), rep("不满意", 20),
rep("满意", 30), rep("不满意", 70))
)
# 对户籍和态度进行分组计数
plot_policy_data <- raw_policy_data %>%
count(type, attitude) %>% # 提取交叉频数特征
arrange(type, attitude)
#绘图
ggplot(plot_policy_data, aes(x = type, y = n, fill = attitude)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_fill_manual(values = c("满意" = "forestgreen", "不满意" = "firebrick")) +
labs(title = "不同户籍群体对政策的满意度分布",
x = "户籍类型",
y = "受访人数 (人)",
fill = "态度") +
theme_minimal()
d). 箱线图 (Box Plot / Box-and-Whisker Plot):一种基于五数概括(最小值、第一四分位数Q1、中位数、第三四分位数Q3、最大值)来显示连续变量分布特征的统计图。箱体表示中间50%的数据(IQR),箱体内的线代表中位数,“须线”延伸至非异常值的极端值,并可识别可能的异常值。箱线图特别适用于比较多个组或分布的整体位置、展布形状以及识别异常值,其优势在于不依赖于特定的分布假定(如正态分布),且占用的空间较小。
例3-9:
我们在研究中对比三类职业群体的日均通勤时间。利用箱线图展示统计分布特征,可以观察到“自由职业”组的通勤时间分布最广,箱体被显著拉长,反映出该群体内部通勤时间的异质性极高;相比之下,“企业职员”组的数据分布相对集中,但存在一个明显的离群值(120分钟),提示存在极端通勤个案;而“公务人员”群体的箱体较短且整体下移,说明其通勤时间不仅更加集中稳定,且整体耗时最少。
library(ggplot2)
library(dplyr)
set.seed(2026)
#构建原始通勤数据
raw_commute_data <- tibble(
group = rep(c("企业职员", "公务人员", "自由职业"), each = 40),
# 模拟正态分布数据
minutes = c(rnorm(40, mean = 40, sd = 10),
rnorm(40, mean = 35, sd = 10),
rnorm(40, mean = 40, sd = 20))
)
# 1.2 数据处理
plot_commute_data <- raw_commute_data %>%
arrange(group) %>%
# 为演示箱线图的“离群值”识别功能,人为替换一组数据为极端值
mutate(minutes = ifelse(group == "企业职员" & row_number() == 1, 120, minutes))
#绘图
ggplot(plot_commute_data, aes(x = group, y = minutes, fill = group)) +
geom_boxplot(alpha = 0.6, outlier.color = "red") +
scale_fill_brewer(palette = "Set2") +
labs(title = "不同职业群体日均通勤时间",
x = "职业群体",
y = "时间 (分钟)",
fill = "职业") +
theme_minimal() +
theme(legend.position = "none")
e).直方图 (Histogram):用于展示连续变量的频率分布特征。它将连续变量的取值范围划分为若干连续的区间(组段),以矩形的面积(底为组距,高为频率/频数密度)表示落入该区间观测值的频数或频率。直方图的核心目的是直观显示数据的分布形态(如是否对称、是否呈钟形、有无偏态)、集中趋势和变异程度。
例3-10:
为了了解居民的心理健康状况,我们收集了 1000 名受访者的主观幸福感量表得分。通过直方图,可以看到数据呈现出典型的正态分布(钟形曲线):大多数人的得分集中在 60 分左右,极低分(20 分以下)和极高分(100 分)的人数非常少。这种分布特征表明,该群体的主观幸福感总体上处于中等偏上的平均水平,且样本内部存在良好的自然变异,符合一般人群心理特征的分布规律。
library(ggplot2)
library(dplyr)
set.seed(2026)
# 生成主观幸福感得分数据
plot_happy_data <- tibble(
score = rnorm(1000, mean = 60, sd = 15)
)
#绘图
ggplot(plot_happy_data, aes(x = score)) +
geom_histogram(
aes(y = after_stat(density)),
binwidth = 5,
fill = "orange",
color = "white",
alpha = 0.8
) +
# 标注理论曲线,增强分析视角
stat_function(fun = dnorm, args = list(mean = 60, sd = 15), color = "darkblue", size = 1) +
labs(title = "居民主观幸福感得分分布",
x = "幸福感量表得分 (0-100)",
y = "概率密度") +
theme_minimal()