Gst Group Training

001 整洁数据分析




桂松涛 Blog
songtaogui@163.com


2025-12-18

什么是数据分析


"已经连续开了十次大了,这次压小绝对一波回本!
"数据分析,就是通过收集、整理、加工和解释数据,从中提取有用的信息和规律,帮助我们做出更好的决策"


明确问题 > 收集数据 > 数据清洗 > 数据挖掘 > 结果呈现

如何做数据分析


if the only tool you have is a hammer, to treat everything as if it were a nail.
– Abraham Maslow



  • 常用公式

  • 数据透视表

  • 条件格式

  • 分析工具包

  • 宏与VBA编程

  • ...



  • 直观

  • 强交互

  • <几十万行

  • 重复操作

  • 学习曲线平缓?, 学习曲线陡峭!


如何做数据分析


基于编程语言的数据分析工具


  • 不直观, 弱交互

  • 高性能:大量数据

  • 数据类型丰富

  • 批量操作

  • 复杂操作

  • 代码复用

  • 学习曲线 陡峭 平缓


向量化






函数式

常见编程范式: 命令式、面向对象、函数式
任务: 给定一个数字列表,计算其中所有“偶数”的“平方”之“和”:
\([1, 2, 3, 4, 5, 6] => 2^2 + 4^2 + 6^2 = 56\)

# 命令式: 强调怎么做(HOW)

    data = [1, 2, 3, 4, 5, 6]

    result = 0
    for x in data
        if x % 2 == 0
            result += x^2
        end
    end

    println(result)


函数式

常见编程范式: 命令式、面向对象、函数式
任务: 给定一个数字列表,计算其中所有“偶数”的“平方”之“和”:
\([1, 2, 3, 4, 5, 6] => 2^2 + 4^2 + 6^2 = 56\)

# 面向对象(python): 对象 = 数据(属性) + 方法(行为)
# 1. 定义对象 (Class)
class NumberContainer:
    def __init__(self, numbers):
        self.numbers = numbers  # 将数据保存在对象的属性中
    def calculate_score(self):  # 定义实例方法
        total = 0
        for x in self.numbers:  # 通过self.numbers 访问数据
            if x % 2 == 0:
                total += x ** 2
        return total

# --- 主程序 ---
data = [1, 2, 3, 4, 5, 6] # 准备数据
# 2. 实例化对象
container = NumberContainer(data)
# 3. 调用方法
result = container.calculate_score()
print(result)


函数式

常见编程范式: 命令式、面向对象、函数式
任务: 给定一个数字列表,计算其中所有“偶数”的“平方”之“和”:
\([1, 2, 3, 4, 5, 6] => 2^2 + 4^2 + 6^2 = 56\)

# 函数式: 分离数据和方法,关注做什么(What), 而非怎么做
function square(x) = x^2
function filter_even(x) = filter(iseven, x)

# 定义组合函数:
function functional_calc(x) = sum(square(filter_even(x)))
# OR:
function functional_calc(x) = x |> filter_even |> square |> sum
# OR:
function functional_calc(x) = sum∘square∘filter_even(x)

# 执行
data = [1, 2, 3, 4, 5, 6]
functional_calc(data)


函数式

常见编程范式: 命令式、面向对象、函数式


函数式编程的好处

  1. 降低认知负担: 确定了输入和函数,输出就确定了,代码结果是可预测的

  2. 关注做什么: 代码核心业务逻辑更清晰

  3. 并发友好: 数据是不可变的,避免死锁

  4. 像搭积木一样组合代码: 高内聚、低耦合 (复用性、易组合)

  5. 开发简单: 易于测试、调试


Dataframe数据结构

Dataframe: 基于向量化 + 函数式的数据分析


Dataframe的基本操作

构建、导入

# 手动构造: 单个元素的列会广播
DataFrame(A=1:3, B=5:7, fied=1)
# 用 => 进行复杂列名DF的构造:
DataFrame("customer age" => [15, 20, 25],
          "first name" => ["A", "B", "C"])

# 从矩阵构造DF:
DataFrame(matrix, names)

# 按列构造:
df = DataFrame()
df.A = 1:8
df.B = ["M", "F", "F", "M", "F", "M", "M", "F"]

# 按行构造:
df = DataFrame(A=Int[], B=String[])
push!(df, (1, "M"))
push!(df, (2, "N"))

# 从其他`Table.jl`的具象格式中转成DF:
df = sqltable |> DataFrame
df = [(a=1, b=2), (a=3, b=4)] |> DataFrame

# 从CSV文件中读取DF:
using CSV
data = CSV.read(path, DataFrame)

基本操作: 选择、过滤、基本信息

# 行列选择
german.Sex
german."Sex"
german[!, :Sex]
german[!, "Sex"]
names(german)
names(german, AbstractString) # 获取指定类型列的名字
propertynames(german) # 获取Symbols格式的名字
eachcol(german) # 按列迭代
empty, empty!   # 删除行, 但保留列数(维度)

mapcols(func, df) # 按列应用func, 返回新df
mapcols(id -> id .^ 2, german)

first(df, n), last(df, n) # n默认是1

# 基本过滤
df[1:3, :]
df[:, [:A, :B]]
df[!, [:A]] # 返回一个df
df[!, :A] # 返回一个Vector

# 正则; Not; Between; All; Cols
df[!, r"x"] # 取出列名包含x的列
df[!, Not(:x1)] # 取出除了`x1`之外的列
df[!, Between(:x1, :x4)]
df[:, All()]
df[:, Cols(x -> startswith(x, "x"))]
# Cols 还可用于给列重排序:
df[:, Cols(r"x", :)] # 把所有包含x的列排在前面, 其他列排在后边
df[:, Cols(Not(r"x"), :)] # 把所有包含x的列排在后面, 其他列排在前边

# 按照数值过滤
df[df.A .> 500, :] #取出所有A列数值大于 500 的行
df[(df.A .> 500) .& (300 .< df.C .< 400), :]
df[in.(df.A, Ref([1, 5, 601])), :]

# DataFrame基本信息
size(df)   # (行, 列)
size(df,1) # 第一维的大小(行数)
size(df,2) # 第二维的大小(列数)
nrow(df), ncol(df)
describe(df) # 基本统计信息mean, min, max, median, nmissing, elementType
describe(df, cols=1:3) # 只统计前三列
show(df, allcols=true)
show(df, allrows=true)


Dataframe的进阶操作



Dataframes的进阶操作: 数据思维
操作分解: 连接、重塑、筛选、排序、修改
管道、向量化、函数式
Split-apply-combine

连接: join



  • innerjoin

  • leftjoin

  • rightjoin

  • crossjoin

  • antijoin

innerjoin(df1, df2, on = :ID) # 大部分join都用同一种语法
innerjoin(df1, df2, on = :ID_df1 => :ID_df2) # 如果要合并的列名字不一样, 用`=>`指示对应关系
crossjoin(df1, df2, makeunique = true) # crossjoin不使用`on`关键词


重塑: 长宽表转换 –> 整洁数据





# stack: 宽表变长表, 会自动进行类型提升
stack(iris, 1:4) # 把1-4列当成variable
stack(iris, [:SepalLength, :SepalWidth, :PetalLength, :PetalWidth])
stack(iris, Not(:Species))
# stack的第三个参数指定需要重复的列(指示列)
stack(iris, [:SepalLength, :SepalWidth], :Species)

# unstack: 长表变宽表
unstack(df::AbstractDataFrame, rowkeys, colkey, value; renamecols::Function=identity,
        allowmissing::Bool=false, allowduplicates::Bool=false, fill=missing)
unstack(df::AbstractDataFrame, colkey, value; renamecols::Function=identity,
        allowmissing::Bool=false, allowduplicates::Bool=false, fill=missing)
unstack(df::AbstractDataFrame; renamecols::Function=identity,
        allowmissing::Bool=false, allowduplicates::Bool=false, fill=missing)

iris.id = 1:size(iris, 1)
longdf = stack(iris, Not([:Species, :id]))
unstack(longdf, :id, :variable, :value)
# 如果剩下的col是unique的, 可以不提供id variable:
unstack(longdf, :variable, :value)
# 甚至可以不提供variable和value:
unstack(longdf)
# 添加view=true, 不copy新数据, 而是创建原数据的view, 会节省内存
stack(iris, view=true)


重塑: 长宽表转换 –> 整洁数据



整洁数据

  • 每个变量构成一列

  • 每个观测构成一行

  • 每个观测的每个变量值构成一个单元格


筛选

Select: 复杂的按列过滤
select(df, Not(:x1))
select(df, r"x")
select(df, :x1 => :a1, :x2 => :a2) # 重命名列
select(df, :x1, :x2 => (x -> x .- minimum(x)) => :x2) # 更改x2的数值
select(df, :x2, :x2 => ByRow(sqrt)) # 生成新的名为:x2_sqrt的列, 存放x2列的开平方值
select(df, AsTable(:) => ByRow(extrema) =>  [:lo, :hi]) # 逐行计算所有列的极值, 存到:lo :hi两列中


修改

transform: 变换
julia> df = DataFrame(x1=[1, 2], x2=[3, 4], y=[5, 6])
2×3 DataFrame
 Row │ x1     x2     y
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      3      5
   2 │     2      4      6

julia> transform(df, All() => +)
2×4 DataFrame
 Row │ x1     x2     y      x1_x2_y_+
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────────
   1 │     1      3      5          9
   2 │     2      4      6         12


修改

transform: 变换
julia> using Statistics

julia> df = DataFrame(x=[1, 2, missing], y=[1, missing, missing])
3×2 DataFrame
 Row │ x        y
     │ Int64    Int64 
─────┼──────────────────
   1 │       1        1
   2 │       2  missing
   3 │ missing  missing

julia> transform(df, AsTable(:) .=>
                    ByRow.([sum∘skipmissing,
                            x -> count(!ismissing, x),
                            mean∘skipmissing]) .=>
                    [:sum, :n, :mean])
3×5 DataFrame
 Row │ x        y        sum    n      mean
     │ Int64    Int64    Int64  Int64  Float64
─────┼─────────────────────────────────────────
   1 │       1        1      2      2      1.0
   2 │       2  missing      2      1      2.0
   3 │ missing  missing      0      0      NaN


排序

sort 和 order


sort!(iris) # 每列都逐级参与排序
sort!(iris, rev = true)
sort!(iris, [:Species, :SepalWidth]) # 指定排序的列
sort!(iris, [order(:Species, by=length), order(:SepalLength, rev=true)]) # 指定排序方式
sort!(iris, [:Species, :PetalLength], rev=[true, false]) # 另一种指定排序方式的语法


管道、向量化、函数式




using Statistics 

df = DataFrame(a = repeat(1:5, outer = 20),
               b = repeat(["a", "b", "c", "d"], inner = 25),
               x = repeat(1:20, inner = 5))
# df
# Row │ a      b       x
#     │ Int64  String  Int64
#─────┼──────────────────────
#   1 │     1  a           1
#   2 │     2  a           1
#   3 │     3  a           1
#   4 │     4  a           1
#   5 │     5  a           1
#   6 │     1  a           2

x_thread = @chain df begin
    @transform(:y = 10 * :x)
    @subset(:a .> 2)
    @by(:b, :meanX = mean(:x), :meanY = mean(:y))
    @orderby(:meanX)
    @select(:meanX, :meanY, :var = :b)
end


Split-Apply-Combine



`Split-Apply-Combine` 数据分析中的经典策略:

  1. 把数据集拆分成不同的分组;

  2. 对某些分组应用特定的方法;

  3. 合并结果成新的数据集;

`Split-Apply-Combine`实现 DataFrame中实现的方式是利用groupby创建GroupedDataFrame数据类型, 然后结合combine, select, transform等操作对齐进行数据处理。

  • groupby: 对DataFrame进行分组

  • combine: 不限制返回行数, 行的顺序取决于group的顺序, 很适合分组计算统计信息

  • select: 返回跟原始df一样的行数和顺序的新列

  • transform: 返回原始df以及追加的新的列


不同编程语言的Dataframe实现




  • Python/Rust: polar >> pandas

  • R: data.table >> dataframe

  • Julia: Dataframes.jl



https://h2oai.github.io/db-benchmark/


Linux命令行下类似Dataframe的操作




  • awk or perl one-liner

  • groupby of bedtools

  • csvtk