统计数据分析
Julia比较适合用作数值计算,
编程既有Python、R、Matlab这样的语言的简洁,
又有C++这样的编译语言的运行效率。
统计数据分析、作图需要用到许多复杂的算法,
有些算法耗时很多,
比如MCMC等。
大量数据的分析、计算、测试都需要易用的编程和高效的运行效率,
Julia在这两点都很适合。
Julia用作统计数据分析,
缺点是其问世时间还比较短,
许多统计模型还没有现成的软件包。
但是,
基本的数据管理、作图、统计分布、随机模拟、最优化等统计计算必须的功能已经很完善,
所以能够自己编写的统计计算任务很适合用Julia编写。
Julia目前的版本是1.7.3。
这一部分在基本Julia语言语法基础上,
从统计数据分析角度简单介绍如何用Julia读入数据,
进行数据清理,汇总,简单的统计分析,
以及一些统计模型和机器学习方法。
参考:
- 数据框(DataFrames)的手册:
https://juliapackages.com/p/dataframes - McNicholas and Tait(2019) Data Science Using Julia, CRC Press.
- Jose Storopoli, Rik Huijzer, Lazaro Alonso(2021) Julia Data Science.
https://cn.julialang.org/JuliaDataScience/
数据分析任务的大部分时间并不是消耗在建模、计算上面,
而是对数据进行整理。
这包括从网络、文本文件、关系数据库、NoSQL数据库等位置获取数据,
将其转换成统计过程需要的数据输入形式,
清理无效数据,
将数据格式进行一致化,
对数据进行简单的探索性分析(EDA),
比如各个属性(变量)的类型,分布,属性之间的关系,
观测之间的分类,
异常值(离群值)识别等。
统计分析最常见的输入数据格式是数据框(Data Frame),
这是与矩阵类似的数据结构,
但是可以同时保存不同类型的数据,
同一列的数据类型相同。
R语言中称为数据框,
SAS语言中称为数据集(dataset),
数据库中称为一个表(table)。
数据框的每一行称为一个观测,
每一列称为一个变量。
Julia语言的DataFrames包(package)提供了数据框数据类型。
如果没有安装此包可用如下命令安装:
import Pkg
Pkg.add("DataFrames")
参考:
- https://juliapackages.com/p/dataframes
- https://github.com/JuliaData/DataFramesMeta.jl
- https://github.com/queryverse/Query.jl
数据框
数据框中的列与统计学中随机变量的分类类似,
分为类别(categorical)变量与数值(continuous)变量。
类别变量主要用来对观测分组,
也可以作为模型中的自变量和因变量。
对类别变量不能进行通常的四则运算和其它数学计算。
字符型列只能作为类别变量,
某些离散取值的数值型列在建模时也可以作为类别变量,
比如,用1代表男性,2代表女性,
这样的列就应该当作类别变量使用。
对数值变量则可以进行各种数学运算。
统计数据与一般的数值计算问题有一个重要差别,
就是统计数据中常常含有缺失值,
如记录遗失、无应答、无交易、不适用等。
用来保存统计数据的数据结构必须能表示缺失值。
Julia语言的missing对象表示缺失值,
缺失值不区分具体类型,
属于Missing数据类型。
4-element Array{Union{Missing, Int64},1}:
1
2
missing
4
数据框生成
数据框各列常用的类型是字符型、整数型和浮点型。
用DataFrame()
可以直接生成小的数据框,如
using DataFrames
da0 = DataFrame(
name=["张三", "李四", "王五", "赵六"],
age=[33, 42, missing, 51],
sex=["M", "F", "M", "M"])
da1 = copy(da0)
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
其中copy()
函数调用制作了一个da0
数据框的副本。
数据框属于可变数据类型(mutable type),
如果直接将da0
赋值给一个变量da1
,
则这两个变量实际指向同一个数据框,
改变其中一个变量会同时修改另一个变量的值。
构造数据框的DataFrame()
函数也可以写成对的输入形式,如:
da0 = DataFrame(
"name" => ["张三", "李四", "王五", "赵六"],
"age" => [33, 42, missing, 51],
"sex" => ["M", "F", "M", "M"])
这时允许列名不符合Julia变量名要求。
可以从一个字典转换为数据框,如:
di = Dict(
"name" => ["张三", "李四", "王五", "赵六"],
"age" => [33, 42, missing, 51],
"sex" => ["M", "F", "M", "M"])
da0 = DataFrame(di)
当所有列都是数值型时,
也可以用一个矩阵转换成数据框,
这时列名自动命名,
然后可以用rename!()
指定列名。
如:
mat1 = [[1, 3, 5, 7] [2, 3, 3, 6]]
4×2 Matrix{Int64}:
1 2
3 3
5 3
7 6
dam1 = DataFrame(mat1, :auto)
4×2 DataFrame
Row │ x1 x2
│ Int64 Int64
─────┼──────────────
1 │ 1 2
2 │ 3 3
3 │ 5 3
4 │ 7 6
rename!(dam1, ["colma", "colmb"])
4×2 DataFrame
Row │ colma colmb
│ Int64 Int64
─────┼──────────────
1 │ 1 2
2 │ 3 3
3 │ 5 3
4 │ 7 6
数据框信息
若df
为数据框,
可以用nrow(df)
求数据框行数(观测个数),
用ncol(df)
求数据框列数(变量个数)。size(df)
返回行列数。
如
(nrow(da1), ncol(da1))
## (4, 3)
用names(df)
返回数据框的变量名字符串数组,
而propertynames(df)
返回变量名的符号数组,如:
@show names(da1);
## names(da1) = ["name", "age", "sex"]
@show propertynames(da1);
## propertynames(da1) = [:name, :age, :sex]
获取每列的类型如:
zip(names(da1), string.(eltype.(eachcol(da1)))) |>
DataFrame |>
d -> rename!(d, ["Variable", "Type"])
3×2 DataFrame
Row │ Variable Type
│ String String
─────┼─────────────────────────────────
1 │ name String
2 │ age Union{Missing, Int64}
3 │ sex String
访问数据框内容
访问单个元素
可以像对矩阵那样取数据框元素或子集。
比如, df[2,1]
表示数据框df
第2行第1列元素:
列序号也可以用变量名符号或者变量名字符串表示,
变量名符号就是将变量名前面添加冒号,
不用字符串表示,如
da1[2,:name]
## "李四"
da1[2,"name"]
## "李四"
vname = "name"
da1[2,vname]
## "李四"
可以修改单个元素的值,
如:
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 孙七 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
访问一列
在行下标位置写单独的叹号表示所有行,
在列下标指定一列后可以访问数据框的这一列,
不制作副本。
比如,df[!,2]
, df[!, "age"]
或df[!, :age]
都可以取出df
的第二列作为一个一维数组:
4-element Vector{Union{Missing, Int64}}:
33
42
missing
51
列序号或列名也可以保存在变量中,如:
或
或
也可以用“数据框名.列名”的格式访问一列,如da1.age
或da1."age"
。
冒号方式
在行下标处使用叹号不制作列向量的副本,
效率较高,
可以修改提取的列的值。
可以用冒号:
作为行下标,
这会生成一列的副本。
如:
da1 = copy(da0)
x = da1[:, :age]
x[:] = x .+ 100
da1
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
可见原始的数据框未被修改。
如果使用叹号格式,则会被修改:
da1 = copy(da0)
x = da1[!, :age]
x[:] = x .+ 100
da1
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 133 M
2 │ 李四 142 F
3 │ 王五 missing M
4 │ 赵六 151 M
叹号格式与冒号格式的选择
当列下标为单个整数、符号、字符串、字符串变量时,
行下标写!
或者写:
都可以取出一列作为数组。
- 叹号格式为“视图”,不制作副本,
修改取出数组同时也会修改原始数据框。 - 冒号格式会制作一列的副本,
除非将冒号格式直接放在赋值的左侧,
都不会修改原始数据框。 - 为安全起见,应使用冒号格式;
对于很大的数据框,
复制会造成较大开销,
可以谨慎地使用叹号格式。
用select选列子集
select
可以挑选列子集,返回副本:
da1 = copy(da0)
select(da1, :name, :age)
4×2 DataFrame
Row │ name age
│ String Int64?
─────┼─────────────────
1 │ 张三 33
2 │ 李四 42
3 │ 王五 missing
4 │ 赵六 51
可以用Not()
说明要去掉的列:
select(da1, Not([:name, :age]))
4×1 DataFrame
Row │ sex
│ String
─────┼────────
1 │ M
2 │ F
3 │ M
4 │ M
即使结果只有一列,select
也会返回数据框。
如下的做法可以将某一列提前到第一列:
4×3 DataFrame
Row │ age name sex
│ Int64? String String
─────┼─────────────────────────
1 │ 33 张三 M
2 │ 42 李四 F
3 │ missing 王五 M
4 │ 51 赵六 M
select!
则会修改输入的数据框,
仅保留指定列。
访问行子集
first
和last
函数
可以用first(df,k)
取出df
前面k
行组成的子集,
用last(df,k)
取出df
最后k
行组成的子集。
da1 = copy(da0)
first(da1, 2)
2×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
2×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 王五 missing M
2 │ 赵六 51 M
逻辑下标
除了用行序号取行子集,
还可以用条件取行子集,
如:
1×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 李四 42 F
注意其中的比较用了加点广播形式。
可以用in.()
判断某列的值是否属于某个子集,
此子集使用Ref()
函数指定。
在使用广播时,用Ref()
使得其自变量被看作标量,
而不需要按元素对应运算。
如:
da0[in.(da0.name, Ref(["张三", "李四"])), :]
2×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
用subset
函数
subset()
函数输入一个数据框和若干个变量名和关于该变量选择行子集的示性函数(返回逻辑值的函数),
返回满足条件的子集副本。
如:
subset(da0, :sex => x -> x .== "M")
3×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 王五 missing M
3 │ 赵六 51 M
如果某一列有缺失值,
则对此列的逻辑判断结果也会有缺失值。
这时可以用coallese()
函数指定缺失时的结果替换值,如:
subset(da0, :age => x -> coalesce.(x .< 45, false),
:sex => x -> x .== "M")
1×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 M
一个更方便的办法是给subset()
函数加skipmissing=true
选项:
subset(da0, :age => x -> x .< 45,
:sex => x -> x .== "M",
skipmissing=true)
1×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 M
用filter
函数
可以用filter(f, df)
取出df的满足条件的行子集,
其中f
是一个以一行作为有名元组类型自变量的示性函数,
如:
filter(row -> row.sex == "F", da1)
1×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 李四 42 F
filter!
则会修改输入的数据框,
仅保留满足条件的行。
访问行列子集
下标方法
对行、列下标可以指定范围,
用下标向量或者变量名符号向量,
如
da1 = copy(da0)
da1[2:4, [1,3]]
3×2 DataFrame
Row │ name sex
│ String String
─────┼────────────────
1 │ 李四 F
2 │ 王五 M
3 │ 赵六 M
注意,da1[2:4, [1,3]]
或da1[2:4, 1:3]
是允许的,
但da1[2:4, (1,3)]
不允许。
多个列下标必须是向量或范围,
不允许使用其它序列。
3×2 DataFrame
Row │ name sex
│ String String
─────┼────────────────
1 │ 李四 F
2 │ 王五 M
3 │ 赵六 M
da1[2:4, ["name", "sex"]]
3×2 DataFrame
Row │ name sex
│ String String
─────┼────────────────
1 │ 李四 F
2 │ 王五 M
3 │ 赵六 M
如果仅取一列,
结果将不再是数据框,
而是普通数组:
3-element Vector{String}:
"F"
"M"
"M"
但是,
如果列下标位置使用数组记号,
则可以取出仅有一列的数据框:
3×1 DataFrame
Row │ sex
│ String
─────┼────────
1 │ F
2 │ M
3 │ M
取行列子集的结果一般不是视图,而是副本。
但是,
在行下标写叹号或者用行序号仅取一行时,
取出的是视图。
视图不生成副本,
修改视图就会修改原始数据框。
视图
视图是数据框的一个子集,
但不制作副本,
修改视图也会修改原始数据框。
对大型数据访问效率更高,
但访问其中一部分时序号进行下标转换,
有一些额外开销。
如:
da1 = copy(da0)
da2v = @view da1[2:4, [:sex, :age]]
3×2 SubDataFrame
Row │ sex age
│ String Int64?
─────┼─────────────────
1 │ F 42
2 │ M missing
3 │ M 51
其它列子集
在列下标位置使用Not()
可以取排除掉某些列后的列子集。如:
da1 = copy(da0)
da1[!, :class] .= 1
da1[!, :ctn] .= "Zhang"
da1
4×5 DataFrame
Row │ name age sex class ctn
│ String Int64? String Int64 String
─────┼────────────────────────────────────────
1 │ 张三 33 M 1 Zhang
2 │ 李四 42 F 1 Zhang
3 │ 王五 missing M 1 Zhang
4 │ 赵六 51 M 1 Zhang
da1[:, Not([:age, :sex])]
4×3 DataFrame
Row │ name class ctn
│ String Int64 String
─────┼───────────────────────
1 │ 张三 1 Zhang
2 │ 李四 1 Zhang
3 │ 王五 1 Zhang
4 │ 赵六 1 Zhang
在列下标位置用Between()
指定两列,
选择这两列之间的列:
da1[:, Between(:age, :class)]
4×3 DataFrame
Row │ age sex class
│ Int64? String Int64
─────┼────────────────────────
1 │ 33 M 1
2 │ 42 F 1
3 │ missing M 1
4 │ 51 M 1
可以用Cols()
将不同的列选择方式合并使用,如:
da1[:, Cols(:name, Between(:sex, :ctn))]
4×4 DataFrame
Row │ name sex class ctn
│ String String Int64 String
─────┼───────────────────────────────
1 │ 张三 M 1 Zhang
2 │ 李四 F 1 Zhang
3 │ 王五 M 1 Zhang
4 │ 赵六 M 1 Zhang
df[:, Cols(:name, :)]
将name
列调到最前面;df[:, Cols(Not(:name), :)]
将name
列调到最后面。
可以在列下标位置使用正则表达式,
选择能匹配的变量名。如:
4×2 DataFrame
Row │ class ctn
│ Int64 String
─────┼───────────────
1 │ 1 Zhang
2 │ 1 Zhang
3 │ 1 Zhang
4 │ 1 Zhang
在程序中,
正则表达式r"^c"
选择以字母c
开头的变量名。
将行列子集转换为矩阵
若取出的某个行列子集A
的元素都是数值型,
可以用hcat(eachcol(A)...)
的方法转换为数值型矩阵。
如:
mat1 = [[1, 3, 5, 7] [2, 3, 3, 6]]
dam1 = DataFrame(mat1, :auto)
mat2sub = hcat(eachcol(dam1[1:2, 1:2])...)
2×2 Matrix{Int64}:
1 2
3 3
添加列
下标方法
可以从一个空数据框逐列地添加数据,如:
da2 = DataFrame()
da2.name = ["张三", "李四", "王五", "赵六"]
da2[!, :age] = [33, 42, missing, 51]
da2[!, "sex"] = ["M", "F", "M", "M"]
da2
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
如果给不存在的一列赋值,
就生成了新的列,
如:
da1 = copy(da0)
da1[!,:group] = [1,1,2,2]
da1
4×4 DataFrame
Row │ name age sex group
│ String Int64? String Int64
─────┼────────────────────────────────
1 │ 张三 33 M 1
2 │ 李四 42 F 1
3 │ 王五 missing M 2
4 │ 赵六 51 M 2
transform
函数
如果需要添加一些统计量列,
可以用transform()
函数,
格式是transform(df, 列名 => 变换函数 => 结果变量名)
。
如:
using Statistics
da1 = copy(da0)
da1[!,:height] .= [166, 182, 173, 171]
da2 = transform(da1, :height => maximum => :max_height)
4×5 DataFrame
Row │ name age sex height max_height
│ String Int64? String Int64 Int64
─────┼─────────────────────────────────────────────
1 │ 张三 33 M 166 182
2 │ 李四 42 F 182 182
3 │ 王五 missing M 173 182
4 │ 赵六 51 M 171 182
也可以对某一列的每一行进行变化,
这时用ByRow()
说明要进行的变换。
可以对多列分别变换。
如:
da1 = copy(da0)
da1[!,:height] .= [166, 182, 173, 171]
da2 = transform(da1,
:height => maximum => :max_height,
:age => ByRow(x -> x + 20) => :newage)
4×6 DataFrame
Row │ name age sex height max_height newage
│ String Int64? String Int64 Int64 Int64?
─────┼──────────────────────────────────────────────────────
1 │ 张三 33 M 166 182 53
2 │ 李四 42 F 182 182 62
3 │ 王五 missing M 173 182 missing
4 │ 赵六 51 M 171 182 71
添加行
可以用push!()
给一个数据框添加新的行,如:
da1 = copy(da0)
push!(da1, ("钱多", 59, "M"))
5×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
5 │ 钱多 59 M
下面这些写法也可以用来添加行:
push!(da1, ["王芳", 40, "F"])
push!(da1, Dict(:name => "邓丽", :age => 18, :sex => "F"))
逐行添加数据是比较低效率的方法,
正常情况下还是应该一次性生成整个数据框。
CSV文件读写
读入
用CSV包的CSV.read()
函数可以读入CSV文件到数据框,
用CSV.write()
函数将数据框保存到CSV文件。
设当前工作目录下有如下的文件class9.csv
:
name,sex,age,height,weight
Sandy,F,11,130.0,23.0
Karen,F,12,143.0,35.0
Kathy,F,12,152.0,38.0
Alice,F,13,144.0,38.0
Thomas,M,11,146.0,39.0
James,M,12,146.0,38.0
John,M,12,150.0,45.0
Robert,M,12,165.0,58.0
Jeffrey,M,13,159.0,38.0
用如下命令将其读入为数据框:
using CSV, DataFrames
d_class = CSV.read("class9.csv", DataFrame)
9×5 DataFrame
Row │ name sex age height weight
│ String7 String1 Int64 Float64 Float64
─────┼───────────────────────────────────────────
1 │ Sandy F 11 130.0 23.0
2 │ Karen F 12 143.0 35.0
3 │ Kathy F 12 152.0 38.0
4 │ Alice F 13 144.0 38.0
5 │ Thomas M 11 146.0 39.0
6 │ James M 12 146.0 38.0
7 │ John M 12 150.0 45.0
8 │ Robert M 12 165.0 58.0
9 │ Jeffrey M 13 159.0 38.0
选项
程序自动判断每一列的类型。
这里String1
,String7
等是固定宽度的字符串类型,
可以提高效率。
为了保险,
用户可以用types
参数指定一个从变量名字符串到类型的字典,如:
using CSV
d_class = CSV.read("class9.csv", DataFrame,
types=Dict(
"name" => String,
"sex" => String,
"height" => Float64,
"weight" => Float64))
如果数据项之间是用一个空格分隔的,
可以加选项用delim=' '
的方式读入。
不支持多个空格作为分隔。
如果数据项之间是用一个制表符分隔的,
可以加选项delim='\t')
的方式读入。
如果数据中没有列名,
可以加选项header=0
。
对于很大的文件,
可以先尝试读入一部分,
这时可以用limit=
选项指定一个读入的行数。
可以用skipto=
指定从那一行开始读入。
更多选项可查看函数的帮助,
命令如“?CSV.Read
”。
从网络读入
可以从网络直接下载CSV文件读入为数据框。
可以用Downloads标准库的download
函数下载临时文件读入,
还可以用HTTP包直接读取。
例如,读入UCI网站的Cleveland心脏病数据数据集:
using Downloads
urlf = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
dht = CSV.read(Downloads.download(urlf), DataFrame,
header=0)
rename!(dht, ["age", "sex", "cp", "trestbps", "chol",
"fbs", "restecg", "thalach", "exang", "oldpeak",
"slope", "ca", "thal", "num"])
该数据集文件的前几行如:
3.0,1.0,1.0,145.0,233.0,1.0,2.0,150.0,0.0,2.3,3.0,0.0,6.0,0
67.0,1.0,4.0,160.0,286.0,0.0,2.0,108.0,1.0,1.5,2.0,3.0,3.0,2
67.0,1.0,4.0,120.0,229.0,0.0,2.0,129.0,1.0,2.6,2.0,2.0,7.0,1
文件第一行没有变量名,
所以用了rename!
添加变量名。
写出
将数据框写入CSV文件class2.csv,命令如
CSV.write("class2.csv", d_class)
如果文件不在当前工作路径,
可以使用全路径如““C:/work/proj/data/class2.csv””。
可以设置当前工作路径,
用“cd()
”命令可以设置当前工作路径,
用“pwd()
”显示当前工作路径,
用“homedir()
”显示用户在操作系统中的主目录。
参考:
- https://csv.juliadata.org/latest/
Excel文件读写
XLSX.jl是完全用Julia实现的读写Excel文件的扩展包。
设当前目录下有class9.xlsx
文件,
数据与class9.csv
的数据相同。
读入如:
d_class2 = DataFrame(XLSX.readtable("class9.xlsx", 1))
9×5 DataFrame
Row │ name sex age height weight
│ Any Any Any Any Any
─────┼───────────────────────────────────
1 │ Sandy F 11 130 23
2 │ Karen F 12 143 35
3 │ Kathy F 12 152 38
4 │ Alice F 13 144 38
5 │ Thomas M 11 146 39
6 │ James M 12 146 38
7 │ John M 12 150 45
8 │ Robert M 12 165 58
9 │ Jeffrey M 13 159 38
转换的数据框没有自动识别各列的类型。
使用identity()
函数对各列的类型进行转换:
d_class2 = identity.(d_class2)
9×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Int64 Int64
─────┼────────────────────────────────────────
1 │ Sandy F 11 130 23
2 │ Karen F 12 143 35
3 │ Kathy F 12 152 38
4 │ Alice F 13 144 38
5 │ Thomas M 11 146 39
6 │ James M 12 146 38
7 │ John M 12 150 45
8 │ Robert M 12 165 58
9 │ Jeffrey M 13 159 38
可以将这两步写在一起:
d_class2 = identity.(DataFrame(XLSX.readtable("class9.xlsx", 1)))
XLSX包可以读取多个工作簿的Excel文件,
可以生成Excel文件。
详见:
- https://felipenoris.github.io/XLSX.jl/stable/tutorial/
数据框变量概括
describe
函数
用describe()
函数对数据框各个变量进行简单概括,
结果为数据框格式。如
5×7 DataFrame
Row │ variable mean min median max nmissing eltype
│ Symbol Union… Any Union… Any Int64 DataType
─────┼──────────────────────────────────────────────────────────────
1 │ name Alice Thomas 0 String
2 │ sex F M 0 String
3 │ age 12.0 11 12.0 13 0 Int64
4 │ height 148.333 130.0 146.0 165.0 0 Float64
5 │ weight 39.1111 23.0 38.0 58.0 0 Float64
describe()
对每个变量给出了数据类型,
缺失值个数,
最大值和最小值,
对数值型变量还给出了均值、中位数。
类似于R语言的summary()
函数。
直接调用统计函数
计算指定的统计量:
using Statistics
mean(d_class[!,"age"])
## 12.0
常用统计函数有mean
,median
,var
, std
, iqr
,minimum
, maximum
,quantile
.
summarystats(x)
函数对数值型变量x
计算各种简单统计量,
以纯文本格式显示。如:
using Statistics, StatsBase
summarystats(d_class[!,"age"])
## 12.0
Summary Stats:
Length: 9
Missing Count: 0
Mean: 12.000000
Minimum: 11.000000
1st Quartile: 12.000000
Median: 12.000000
3rd Quartile: 12.000000
Maximum: 13.000000
两个变量可以用cov(x,y)
计算协方差,cor(x,y)
计算相关系数。
对于矩阵形式的随机向量观测值
X,
用cov(X)
估计协方差阵,
用cor(X)
估计相关系数矩阵。
如:
cov(hcat(eachcol(dcl)...))
3×3 Matrix{Float64}:
0.5 3.375 1.75
3.375 100.25 79.2083
1.75 79.2083 84.1111
combine
函数
可以用combine
函数计算统计量。
输入数据框,
以及用变量名 => 统计函数
或者变量名 => 统计函数 => 结果变量名
方式指定的一个或多个要汇总的内容。
比如,计算身高和体重的平均值:
combine(d_class, :height => mean, :weight => mean)
1×2 DataFrame
Row │ height_mean weight_mean
│ Float64 Float64
─────┼──────────────────────────
1 │ 148.333 39.1111
上例可以用.=>
的语法合并写在一起:
combine(d_class, [:height, :weight] .=> mean)
可以用names(df) .=> 统计函数
的格式指定对所有列计算同一统计量。
如:
subdf = d_class[:,Cols(:age, :height, :weight)]
combine(subdf,
names(subdf) .=> mean)
1×3 DataFrame
Row │ age_mean height_mean weight_mean
│ Float64 Float64 Float64
─────┼────────────────────────────────────
1 │ 12.0 148.333 39.1111
计算三个变量的平均值和最大值,写成链式调用,
用names(df)
方法指定所有变量:
using Statistics
select(d_class, :age, :height, :weight) |>
subdf -> combine(subdf,
names(subdf) .=> mean,
names(subdf) .=> maximum)
1×6 DataFrame
Row │ age_mean height_mean weight_mean age_maximum height_maximum weight_maximum
│ Float64 Float64 Float64 Int64 Float64 Float64
─────┼─────────────────────────────────────────────────────────────────────────────────
1 │ 12.0 148.333 39.1111 13 165.0 58.0
上例的另一种写法:
combine(d_class,
([:age, :height, :weight] .=> [mean maximum])...)
简单修改
修改变量名
用rename!()
函数可以为数据框变量改名,
如
da1 = copy(da0)
rename!(da1, [:sex => :性别, :age => :年龄])
4×3 DataFrame
Row │ name 年龄 性别
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 missing M
4 │ 赵六 51 M
或:
da1 = copy(da0)
rename!(da1, ["name", "年龄", "性别"])
修改或添加变量
可以直接修改变量或者添加新变量,如
dcl = copy(d_class)
dcl[!,:height] ./= 100
first(dcl, 3)
3×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Float64 Float64
─────┼─────────────────────────────────────────
1 │ Sandy F 11 1.3 23.0
2 │ Karen F 12 1.43 35.0
3 │ Kathy F 12 1.52 38.0
dcl = copy(d_class)
dcl[!,:ratio] = dcl[!,:weight] ./ dcl[!,:height]
dcl[!,:classno] .= 1
first(dcl,3)
3×7 DataFrame
Row │ name sex age height weight ratio classno
│ String String Int64 Float64 Float64 Float64 Int64
─────┼────────────────────────────────────────────────────────────
1 │ Sandy F 11 130.0 23.0 0.176923 1
2 │ Karen F 12 143.0 35.0 0.244755 1
3 │ Kathy F 12 152.0 38.0 0.25 1
其中dcl[!,:classno] .= 1
用了广播的方法将一列赋值为同一个值,
并生成了新的列。
dcl[!,:ratio] = dcl[!,:weight] ./ dcl[!,:height]
添加了新变量,
用=
或者用.=
赋值都是同样效果,
用!
或者用:
也是相同的效果。
但是,如果这一列已经存在,
赋值左侧用!
格式时,
不论用=
还是用.=
都是重新绑定此列到赋值的右侧,
可以改变此列的类型。
这时如果左侧是用的冒号,
则用.=
不改变此列的元素类型,
而=
可以修改此列的元素类型。
如果将dcl[!,:ratio]
赋值为一个保存了向量值的变量x
,
则这时执行的是一个重复绑定,
不制作x
的副本,
所以修改彼此的值会同时修改对方。
函数select!
将数据框变成列子集,filter!
将数据框变成行子集。
用transform!
修改列
可以用transform!
修改原有的列,
加renamecols = false
选项。
比如:
dcl = copy(d_class)
transform!(dcl, :height => (x -> x ./ 100),
renamecols = false)
9×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Float64 Float64
─────┼──────────────────────────────────────────
1 │ Sandy F 11 1.3 23.0
2 │ Karen F 12 1.43 35.0
3 │ Kathy F 12 1.52 38.0
4 │ Alice F 13 1.44 38.0
5 │ Thomas M 11 1.46 39.0
6 │ James M 12 1.46 38.0
7 │ John M 12 1.5 45.0
8 │ Robert M 12 1.65 58.0
9 │ Jeffrey M 13 1.59 38.0
上面的程序将数据框中的身高改成了以米为单位。
如果希望修改后使用新变量名,
方法如:
dcl = copy(d_class)
transform!(dcl, :height => (x -> x ./ 100) => :height_m)
9×6 DataFrame
Row │ name sex age height weight height_m
│ String String Int64 Float64 Float64 Float64
─────┼────────────────────────────────────────────────────
1 │ Sandy F 11 130.0 23.0 1.3
2 │ Karen F 12 143.0 35.0 1.43
3 │ Kathy F 12 152.0 38.0 1.52
4 │ Alice F 13 144.0 38.0 1.44
5 │ Thomas M 11 146.0 39.0 1.46
6 │ James M 12 146.0 38.0 1.46
7 │ John M 12 150.0 45.0 1.5
8 │ Robert M 12 165.0 58.0 1.65
9 │ Jeffrey M 13 159.0 38.0 1.59
修改多列
可以用mapcols()
对数据框的所有列应用某种变换,
返回变换结果。
如:
dc1 = copy(d_class)
dc2 = mapcols(x -> x ./100, dc1[!, ["age", "height", "weight"]])
first(dc2, 3)
3×3 DataFrame
Row │ age height weight
│ Float64 Float64 Float64
─────┼───────────────────────────
1 │ 0.11 1.3 0.23
2 │ 0.12 1.43 0.35
3 │ 0.12 1.52 0.38
替换值
用replace!()
函数对数据框指定的列的某些值进行替换。如:
da1 = copy(da0)
replace!(da1.sex, "F" => "女", "M" => "男")
da1
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼─────────────────────────
1 │ 张三 33 男
2 │ 李四 42 女
3 │ 王五 missing 男
4 │ 赵六 51 男
replace!(da1.age, missing => 0); da1
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 男
2 │ 李四 42 女
3 │ 王五 0 男
4 │ 赵六 51 男
replace!
函数也可以输入一个替换函数,
按该替换函数的映射将第二自变量进行值替换,如:
da1 = copy(da0)
replace!(x -> ismissing(x) ? 0 : x, da1[!, :age])
da1
4×3 DataFrame
Row │ name age sex
│ String Int64? String
─────┼────────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 王五 0 M
4 │ 赵六 51 M
将某些值替换为missing
,
需要先用allowmissing!
将整个数据框的所有列(或指定列)允许有缺失值,
然后使用.=
赋值。如:
da1 = copy(da0)
allowmissing!(da1)
replace!(x -> x == "F" ? missing : x, da1[!, :sex])
da1
4×3 DataFrame
Row │ name age sex
│ String? Int64? String?
─────┼───────────────────────────
1 │ 张三 33 M
2 │ 李四 42 missing
3 │ 王五 missing M
4 │ 赵六 51 M
对所有列如果要统一替换,
可以选择整个数据框。如:
da2 = DataFrame(x = [1, 2, 0, 4],
y = [11, 0, 33, 44])
allowmissing!(da2)
da2 .= ifelse.(da2 .== 0, missing, da2)
4×2 DataFrame
Row │ x y
│ Int64? Int64?
─────┼──────────────────
1 │ 1 11
2 │ 2 missing
3 │ missing 33
4 │ 4 44
排序
用sort!()
函数将数据框安装某一列或某几列排序。
这个函数会修改其输入,
输入的数据框会被修改为排序后的值。
用第二个自变量指定按哪一列排序:
dcl = copy(d_class)
typeof(dcl)
## DataFrame
sort!(dcl, :age)
dcl
9×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Float64 Float64
─────┼──────────────────────────────────────────
1 │ Sandy F 11 130.0 23.0
2 │ Thomas M 11 146.0 39.0
3 │ Karen F 12 143.0 35.0
4 │ Kathy F 12 152.0 38.0
5 │ James M 12 146.0 38.0
6 │ John M 12 150.0 45.0
7 │ Robert M 12 165.0 58.0
8 │ Alice F 13 144.0 38.0
9 │ Jeffrey M 13 159.0 38.0
可以指定多列,当前一列相同时按后一列排序:
sort!(dcl, [:sex, :age])
dcl
9×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Float64 Float64
─────┼──────────────────────────────────────────
1 │ Sandy F 11 130.0 23.0
2 │ Karen F 12 143.0 35.0
3 │ Kathy F 12 152.0 38.0
4 │ Alice F 13 144.0 38.0
5 │ Thomas M 11 146.0 39.0
6 │ James M 12 146.0 38.0
7 │ John M 12 150.0 45.0
8 │ Robert M 12 165.0 58.0
9 │ Jeffrey M 13 159.0 38.0
在指定排序变量时,
可以用order()
函数指定选项,
如rev=true
指定降序,by=uppercase
指定按转换为大写后排序。
例如
sort!(dcl, [:sex, order(:age, rev=true)])
dcl
9×5 DataFrame
Row │ name sex age height weight
│ String String Int64 Float64 Float64
─────┼──────────────────────────────────────────
1 │ Alice F 13 144.0 38.0
2 │ Karen F 12 143.0 35.0
3 │ Kathy F 12 152.0 38.0
4 │ Sandy F 11 130.0 23.0
5 │ Jeffrey M 13 159.0 38.0
6 │ James M 12 146.0 38.0
7 │ John M 12 150.0 45.0
8 │ Robert M 12 165.0 58.0
9 │ Thomas M 11 146.0 39.0
函数sort()
对输入数据框排序并输出排序后的副本,
不改变输入的数据框。
纵向合并
如果两个数据框df1和df2结构相同,
只是保存了不同的观测,
可以用vcat(df1, df2)
返回上下合并的结果,
如:
df1 = DataFrame(
id = [1, 2],
x = [101, 102])
df2 = DataFrame(
id = [3, 4],
x = [201, 202])
vcat(df1, df2)
4×2 DataFrame
Row │ id x
│ Int64 Int64
─────┼──────────────
1 │ 1 101
2 │ 2 102
3 │ 3 201
4 │ 4 202
可以用append!(df1, df2)
将内容合并到df1中。
如:
4×2 DataFrame
Row │ id x
│ Int64 Int64
─────┼──────────────
1 │ 1 101
2 │ 2 102
3 │ 3 201
4 │ 4 202
横向合并
如果两个数据框行数相同,
按行号一对一左右合并,
可以用hcat(x, y)
合并。
innerjoin()
等函数可以用来按照关键列对两个数据框进行横向合并。
一对一横向合并
dfj1 = DataFrame(id=["a", "b"], X=[11,12])
2×2 DataFrame
Row │ id X
│ String Int64
─────┼───────────────
1 │ a 11
2 │ b 12
dfj2 = DataFrame(id=["b", "a"], Y=[21,22])
Row │ id Y
│ String Int64
─────┼───────────────
1 │ b 21
2 │ a 22
dfj3 = innerjoin(dfj1, dfj2, on = :id)
2×3 DataFrame
Row │ id X Y
│ String Int64 Int64
─────┼──────────────────────
1 │ b 12 21
2 │ a 11 22
如果用来连接的关键列名字不一样,
可以提供对作为on
的输入,如on = :id => :num
。
如果有多个关键列,
可以用对的向量作为on
的输入。
如果确信应该是作一对一合并,
想检查实际数据是否满足如此要求,
可以加选项validate = (true, true)
进行验证,
验证失败则出错停止。
如果两个数据框有非关键列但是变量名相同,
可以加选项makeunique = true
使得后面的数据框自动修改变量名以避免列名冲突。
一对多横向合并
关键变量的一个值可以和多个值匹配。如:
dfj4 = DataFrame(id=["a", "a", "b"], Y=[41,42,43])
3×2 DataFrame
Row │ id Y
│ String Int64
─────┼───────────────
1 │ a 41
2 │ a 42
3 │ b 43
dfj5 = innerjoin(dfj1, dfj4, on = :id)
3×3 DataFrame
Row │ id X Y
│ String Int64 Int64
─────┼──────────────────────
1 │ a 11 41
2 │ a 11 42
3 │ b 12 43
多对多横向合并
关键变量的多个值可以和多个值两两搭配。如:
dfj6 = DataFrame(id=["a", "a", "b"], X=[61,62,63])
3×2 DataFrame
Row │ id X
│ String Int64
─────┼───────────────
1 │ a 61
2 │ a 62
3 │ b 63
dfj7 = DataFrame(id=["a", "a", "b"], Y=[71,72,73])
3×2 DataFrame
Row │ id Y
│ String Int64
─────┼───────────────
1 │ a 71
2 │ a 72
3 │ b 73
dfj8 = innerjoin(dfj6, dfj7, on = :id)
5×3 DataFrame
Row │ id X Y
│ String Int64 Int64
─────┼──────────────────────
1 │ a 61 71
2 │ a 62 71
3 │ a 61 72
4 │ a 62 72
5 │ b 63 73
连接
innerjoin(a,b, on=...)
默认将左右能匹配的观测保留,
不能匹配的删除,
这在数据库术语中称为“内连接”(inner join)。leftjoin
作左连接,即保留左侧数据框所有观测,
右侧数据框仅保留能匹配的观测。rightjoin
作右连接,保留右侧数据框所有观测,
左侧数据框仅保留能匹配的观测。outerjoin
作全外连接。保留两侧数据框所有观测。semijoin
仅保留左侧数据框能匹配的观测,
不保留右侧数据框内容,
实际是用右侧数据框的键值来选择左侧数据框的内容。antijoin
仅保留左侧数据框不能匹配的观测,
不保留右侧数据框内容,
也是用右侧数据框的键值来排除左侧数据框的内容。
dfj9 = DataFrame(id=["a", "b"], X=[91,92])
2×2 DataFrame
Row │ id X
│ String Int64
─────┼───────────────
1 │ a 91
2 │ b 92
dfj10 = DataFrame(id=["a", "c"], Y=[101,102])
2×2 DataFrame
Row │ id Y
│ String Int64
─────┼───────────────
1 │ a 101
2 │ c 102
leftjoin(dfj9, dfj10, on=:id)
2×3 DataFrame
Row │ id X Y
│ String Int64 Int64?
─────┼────────────────────────
1 │ a 91 101
2 │ b 92 missing
rightjoin(dfj9, dfj10, on=:id)
2×3 DataFrame
Row │ id X Y
│ String Int64? Int64
─────┼────────────────────────
1 │ a 91 101
2 │ c missing 102
outerjoin(dfj9, dfj10, on=:id)
3×3 DataFrame
Row │ id X Y
│ String Int64? Int64?
─────┼──────────────────────────
1 │ a 91 101
2 │ b 92 missing
3 │ c missing 102
在左连接、右连接或外连接时,
可以用source = 列名
选项使得输出中增加一项用来表示当前观测是否仅来自左侧、仅来自右侧还是来自两侧。如:
outerjoin(dfj9, dfj10, on=:id, source = :source)
3×4 DataFrame
Row │ id X Y source
│ String Int64? Int64? String
─────┼──────────────────────────────────────
1 │ a 91 101 both
2 │ b 92 missing left_only
3 │ c missing 102 right_only
crossjoin
则对输入的两个或多个数据框找到观测的所有组合,
输出组合的结果。
不使用on
自变量。
如:
dfc1 = DataFrame(a = [1,2,3], b=["a", "a", "b"])
3×2 DataFrame
Row │ a b
│ Int64 String
─────┼───────────────
1 │ 1 a
2 │ 2 a
3 │ 3 b
dfc2 = DataFrame(c = [21,22])
2×1 DataFrame
Row │ c
│ Int64
─────┼───────
1 │ 21
2 │ 22
6×3 DataFrame
Row │ a b c
│ Int64 String Int64
─────┼──────────────────────
1 │ 1 a 21
2 │ 1 a 22
3 │ 2 a 21
4 │ 2 a 22
5 │ 3 b 21
6 │ 3 b 22
长宽表转换
stack
和unstack
对于多个个体(subject)的若干个变量的多次测量值,
如果将多次观测值放在不同行保存,
就称为长表格式。
如果将多次测量值放在同一行保存,
就称为宽表格式。
统计建模程序一般使用长表格式。
Julia的DataFrames包具有比较一般的长宽表转换功能,
能够解决最常见的长宽表转换问题。
目前还没有R的dplyr包、SAS的proc transpose功能那么强。
stack
函数
stack
函数将同一行的不同列堆叠到同一列中,
实现宽表到长表的转换。
比如,下面是一个宽表:
dw1 = DataFrame(id=["a", "b", "c"],
x1 = [11, 12, 13], x2=[21, 22, 23])
3×3 DataFrame
Row │ id x1 x2
│ String Int64 Int64
─────┼──────────────────────
1 │ a 11 21
2 │ b 12 22
3 │ c 13 23
dw1t = stack(dw1, [:x1, :x2], :id)
6×3 DataFrame
Row │ id variable value
│ String String Int64
─────┼─────────────────────────
1 │ a x1 11
2 │ b x1 12
3 │ c x1 13
4 │ a x2 21
5 │ b x2 22
6 │ c x2 23
stack
第一自变量是要转换的宽表,
第二自变量用向量给出要转换的列集合,
第三自变量指定一个或多个分组变量,
长宽表转换是针对相同的分组变量值进行转换的。
结果数据框中自动命名的variable
变量用来表示原来的列名,value
表示该列的值。
上例的结果中variable
表示时间,
如果要转换成数值的时间,
可以用:
transform!(dw1t, :variable => ByRow(s -> parse(Int, s[2:2])) => :time)
select!(dw1t, Not(:variable))
6×3 DataFrame
Row │ id value time
│ String Int64 Int64
─────┼──────────────────────
1 │ a 11 1
2 │ b 12 1
3 │ c 13 1
4 │ a 21 2
5 │ b 22 2
6 │ c 23 2
unstack
函数
unstack
函数将放在同一列的多个测量值转为存放在同一行,
并适当命名。如:
d1n = DataFrame(id=["a", "a", "b", "b", "c", "c"],
time = [1,2,1,2,1,2],
value=[11,12, 21,22, 31,32])
6×3 DataFrame
Row │ id time value
│ String Int64 Int64
─────┼──────────────────────
1 │ a 1 11
2 │ a 2 12
3 │ b 1 21
4 │ b 2 22
5 │ c 1 31
6 │ c 2 32
将每个id两次测量值(value
)转为存放到同一行:
d1nw = unstack(d1n, :id, :time, :value)
3×3 DataFrame
Row │ id 1 2
│ String Int64? Int64?
─────┼────────────────────────
1 │ a 11 12
2 │ b 21 22
3 │ c 31 32
unstack()
的第一自变量是要转换的长表,
第二自变量是分组变量,
转换在分组变量指定的每组内进行,
第三自变量是用来区分不同测量值的标识,
第四自变量是实际测量值。
上面的转换结果用了1
, 2
这样的列名,
为了使用合法变量名,程序如:
d1n2 = transform(d1n,
:time => ByRow(s -> string("x", s)) => :variable)
select!(d1n2, Not(:time))
d1nw = unstack(d1n2, :id, :variable, :value)
3×3 DataFrame
Row │ id x1 x2
│ String Int64? Int64?
─────┼────────────────────────
1 │ a 11 12
2 │ b 21 22
3 │ c 31 32
转换实例
考虑如下数据:
"subject","x_1","x_2","x_3","y_1","y_2","y_3"
1,5,7,8,9,7,6
2,8,2,10,1,1,9
3,7,2,5,10,8,3
4,1,5,6,10,1,1
5,9,7,10,8,8,10
读入为数据框:
using CSV
dwide = CSV.read("widetab.csv", DataFrame)
5×7 DataFrame
Row │ subject x_1 x_2 x_3 y_1 y_2 y_3
│ Int64 Int64 Int64 Int64 Int64 Int64 Int64
─────┼───────────────────────────────────────────────────
1 │ 1 5 7 8 9 7 6
2 │ 2 8 2 10 1 1 9
3 │ 3 7 2 5 10 8 3
4 │ 4 1 5 6 10 1 1
5 │ 5 9 7 10 8 8 10
这是5位病人3次去医院的记录数据,
每次去有x
、y
两个测量值。
用stack()
函数将其中的测量值合并到一列value
,
并增加一列表示值所对应的变量名的字符型列。
第二自变量指定要合并的列,可以用列号范围、列号向量、列号名称。
第三自变量可选,指定保持不变的区分病人的变量。
如
dlong4w = stack(dwide, 2:7, :subject)
30×3 DataFrame
Row │ subject variable value
│ Int64 String Int64
─────┼──────────────────────────
1 │ 1 x_1 5
2 │ 2 x_1 8
3 │ 3 x_1 7
4 │ 4 x_1 1
⋮ │ ⋮ ⋮ ⋮
27 │ 2 y_3 9
28 │ 3 y_3 3
29 │ 4 y_3 1
30 │ 5 y_3 10
22 rows omitted
也可以写成
dlong4w = stack(dwide, [:x_1, :x_2, :x_3, :y_1, :y_2, :y_3], :subject)
或
dlong4w = stack(dwide, Cols(r"^x_", r"^y_"), :subject)
这个表格的形式虽然是长表,
但并不是我们最终所求。
我们希望将变量名与随访时间分为两个变量,
先写一个按下划线拆分变量名与时间的函数:
function make_split(sep="_")
function f(x)
y = split(x, sep)
return (string(y[1]), parse(Int, y[2]))
end
end
transform!(dlong4w,
:variable => (x -> make_split("_").(x)) => [:varname, :time])
select!(dlong4w, Not(:variable))
30×4 DataFrame
Row │ subject value varname time
│ Int64 Int64 String Int64
─────┼────────────────────────────────
1 │ 1 5 x 1
2 │ 2 8 x 1
3 │ 3 7 x 1
4 │ 4 1 x 1
⋮ │ ⋮ ⋮ ⋮ ⋮
27 │ 2 9 y 3
28 │ 3 3 y 3
29 │ 4 1 y 3
30 │ 5 10 y 3
22 rows omitted
按照病人号、随访时间、测量值名排序,长表现在变成了:
sort!(dlong4w, [:subject, :time, :varname] )
dlong4w
30×4 DataFrame
Row │ subject value varname time
│ Int64 Int64 String Int64
─────┼────────────────────────────────
1 │ 1 5 x 1
2 │ 1 9 y 1
3 │ 1 7 x 2
4 │ 1 7 y 2
⋮ │ ⋮ ⋮ ⋮ ⋮
27 │ 5 7 x 2
28 │ 5 8 y 2
29 │ 5 10 x 3
30 │ 5 10 y 3
用unstack()
可以将放在同一列的变量值合并到一行中。
第二自变量表示按照那些变量分组,
第三自变量表示保存了变量名的列(类型是Symbol),
第四自变量是保存的值。
如
dlong4w2 = unstack(dlong4w, [:subject, :time], :varname, :value)
15×4 DataFrame
Row │ subject time x y
│ Int64 Int64 Int64? Int64?
─────┼────────────────────────────────
1 │ 1 1 5 9
2 │ 1 2 7 7
3 │ 1 3 8 6
4 │ 2 1 8 1
⋮ │ ⋮ ⋮ ⋮ ⋮
12 │ 4 3 6 1
13 │ 5 1 9 8
14 │ 5 2 7 8
15 │ 5 3 10 10
7 rows omitted
在获得了长表格式后,可以进一步用groupby
和combine
作分组汇总。
比如,计算每个病人x
, y
的平均值:
groupby(dlong4w2, :subject) |>
gdf -> combine(gdf, :x => mean, :y => mean)
5×3 DataFrame
Row │ subject x_mean y_mean
│ Int64 Float64 Float64
─────┼───────────────────────────
1 │ 1 6.66667 7.33333
2 │ 2 6.66667 3.66667
3 │ 3 4.66667 7.0
4 │ 4 4.0 4.0
5 │ 5 8.66667 8.66667
计算每个时间的测量值均值:
groupby(dlong4w2, :time) |>
gdf -> combine(gdf, :x => mean, :y => mean)
3×3 DataFrame
Row │ time x_mean y_mean
│ Int64 Float64 Float64
─────┼─────────────────────────
1 │ 1 6.0 7.6
2 │ 2 4.6 5.0
3 │ 3 7.8 5.8
总结
如果使用R的tidyr包,
可以用pivot_longer
和pivot_wider
更容易地完成转换。
SAS的PROC TRANSPOSE也可以比较容易地完成这个数据的转换。
总之,
Julia DataFrames的宽表变长表用stack()
函数,
长表变宽表用unstack()
函数,
拆分变量名与时间可以用字符串功能以及类型转换功能来实现。
变量名在Julia中是Symbol数据类型,
不是字符串。
用String()
将Symbol类型转换成字符串类型,
用parse(Int, s)
将字符串s
转换成整数,
用parse(Float64, s)
将字符串s
转换成浮点数。
缺失值管理
Julia语言提供了missing
表示缺失值,missing
的功能与R语言中的NA
,
数据库SQL语言中的null
类似。
其数据类型为Missing
。
数据框中可以用missing
表示缺失元素,
见上面的da1
数据框。
但是,如果数据框中某列原来没有缺失值,
则不能将其中的值赋值为missing
。
用ismissing()
判断某个值是否缺失值,
加点以后可以判断某个向量的每个元素,如
da1 = copy(da0)
ismissing.(da1[!,:age]) |> show
## Bool[0, 0, 1, 0]
函数any()
可以判断布尔值向量元素是否存在真值,all()
可以判断布尔值向量元素是否都是真值。如
any(ismissing.(da1[!,:age]))
## true
有missing
参与的四则运算、比较、数学计算一般返回缺失值。
用skipmissing()
可以在计算时删除指定列中的缺失值进行计算,
如
4-element Vector{Union{Missing, Int64}}:
33
42
missing
51
sum(da1[!,:age])
## missing
sum(skipmissing(da1[!,:age]))
## 126
用coalesce.()
可以将缺失值替换为指定的值:
coalesce.(da1[!,:age], 0) |> show
## [33, 42, 0, 51]
用Missings.replace()
输出一个迭代器,
可以在计算中用指定值替换缺失值,如
sum(Missings.replace(da1[!,:age], 0))
## 126
为了获得整个数据框df
中每行是否不含任何缺失值的示性值(1表示没有缺失值,0表示有),
用DataFrames.completecases(df)
函数。
如:
DataFrames.completecases(da1) |> show
## Bool[1, 1, 0, 1]
将整个数据框中含有缺失值的观测都删去,
用DataFrames.dropmissing!(df)
函数。DataFrames.dropmissing(df)
则返回副本。
如
da1 = copy(da0)
dropmissing!(da1)
da1
3×3 DataFrame
Row │ name age sex
│ String Int64 String
─────┼───────────────────────
1 │ 张三 33 M
2 │ 李四 42 F
3 │ 赵六 51 M
skipmissing()
的输出是一个迭代器,
可以当作一个普通向量参与运算;
为了将去掉缺失值后的内容转换成一个普通向量,
用skipmissing()
和collect()
函数复合,如
da1 = copy(da0)
collect(skipmissing(da1[!,:age])) |> show
## [33, 42, 51]
也可以用函数复合运算写成:
(collect ∘ skipmissing)(da1[!,:age]) |> show
## [33, 42, 51]
含有缺失值的向量实际是Array{Union{Missing, T},1}
类型,
其中T
是String, Int64, Float64等值类型。如
typeof(da1[!,:age])
## Vector{Union{Missing, Int64}}
## (alias for Array{Union{Missing, Int64}, 1})
用Missings.nonmissingtype(eltype(x))
函数可以求带有missing
值的向量x
中非缺失值的数据类型,如
Missings.nonmissingtype(eltype(da1[!,:age]))
## Int64
如果df
是数据框,则DataFrames.allowmissing!(df)
使得数据框中所有列都允许取缺失值。DataFrames.allowmissing!(df, col)
可以使得第col
列取缺失值,DataFrames.allowmissing!(df, cols)
可以使得下标向量cols
指定的那些列允许取缺失值。DataFrames.disallowmissing!(df)
函数起到反向的作用。
如
da1 = copy(da0)
DataFrames.allowmissing!(da1, 2:3)
typeof(da1[!,:sex])
## Vector{Union{Missing, String}}
## (alias for Array{Union{Missing, String}, 1})
分类变量
介绍
某些统计模型允许使用分类变量,
比如,
逻辑斯谛回归的因变量是分类变量。
用字符串表示分类变量很直观,
但是不方便用在数学模型计算当中,
在数据量很大时保存许多有重复的字符串往往也比较浪费存储空间。
CategoricalArrays包提供了CategoricalArray(分类数组)类型,
专门用来表示分类变量。
这种类型类似于R的因子(factor)类型,
将分类变量的不同值编码为整数号码,
然后保存这些号码以及号码与变量实际值之间的对应表。
参见:
- https://categoricalarrays.juliadata.org/stable/
生成
用categorical()
函数将分类变量转换成分类数组类型,
允许有缺失值(missing)。
如
using CategoricalArrays
dcl = copy(d_class);
dcl[!,:sex] = categorical(dcl[!,:sex]);
show(dcl[!,:sex])
## CategoricalValue{String, UInt32}[
## "F", "F", "F", "F", "M", "M", "M", "M", "M"]
typeof(dcl[!,:sex])
## CategoricalVector{String, UInt32, String,
## CategoricalValue{String, UInt32}, Union{}}
## (alias for CategoricalArray{String, 1, UInt32, String,
## CategoricalValue{String, UInt32}, Union{}})
水平值
用levels()
求分类数组的各个不同值,如
levels(dcl[!,:sex]) |> show
## ["F", "M"]
用levels!()
修改因子水平的次序,如:
sexc = dcl[:,:sex] # 副本
levels!(sexc, ["M", "F"])
sexc |> show
## CategoricalValue{String, UInt32}[
## "F", "F", "F", "F", "M", "M", "M", "M", "M"]
生成时加ordered=true
选项使分类变量为有序,
可以比较次序。
也可以用ordered!(x, true)
设置为有序。
不论是否有序都可以按照分类变量排序,
排序时按因子水平(levels)排列。
用recode!()
给已有的分类变量重新指定标签,
如:
recode!(sexc, "F" => "女", "M" => "男")
sexc |> show
## CategoricalValue{String, UInt32}[
## "女", "女", "女", "女", "男", "男", "男", "男", "男"]
转换
许多Julia软件包不支持分类变量,
这时,可以用levelcode.()
将分类变量转换成其编码的整数值,如:
sexi = levelcode.(dcl[:,:sex]);
show(sexi)
## [1, 1, 1, 1, 2, 2, 2, 2, 2]
将类别变量转换为其显示字符串的函数:
cat2string = x -> levels(x)[levelcode.(x)]
cat2string(dcl[:,:sex]) |> show
## ["F", "F", "F", "F", "M", "M", "M", "M", "M"]
频数统计
若x
为整型变量,StatsBase.counts(x)
对x
的最小值到最大值的所有整数值计算频数,
如:
using StatsBase
dcl = copy(d_class)
StatsBase.counts(dcl[:,:age]) |> show
## [2, 5, 2]
将值与频数对应:
dcl[:,:age] |> x -> DataFrame(
age = minimum(x):maximum(x),
count = StatsBase.counts(x))
3×2 DataFrame
Row │ age count
│ Int64 Int64
─────┼──────────────
1 │ 11 2
2 │ 12 5
3 │ 13 2
函数StatsBase.frequency(x)
计算比例。
对于更一般的类型如字符串,counts
和frequency
不支持,
可以使用更一般的StatsBase.countmap(x)
函数,
结果为变量值到频数值的字典,
如:
StatsBase.countmap(dcl[:,:age]) |> show
## Dict(13 => 2, 11 => 2, 12 => 5)
countmap
输出一个字典,
缺点是不能排序。
利用这个函数写一个频数函数:
function freqd(x)
di = StatsBase.countmap(x)
d = DataFrame(x = collect(keys(di)), count = collect(values(di)))
sort!(d)
return d
end
freqd(dcl[:,:age])
3×2 DataFrame
Row │ x count
│ Int64 Int64
─────┼──────────────
1 │ 11 2
2 │ 12 5
3 │ 13 2
2×2 DataFrame
Row │ x count
│ String Int64
─────┼───────────────
1 │ F 4
2 │ M 5
dcl[!,:sex] = categorical(dcl[!,:sex])
freqd(dcl[:,:sex])
2×2 DataFrame
Row │ x count
│ Cat… Int64
─────┼─────────────
1 │ F 4
2 │ M 5
日期和时间类型
有些数据中包括日期,
或者日期时间。
Julia的Dates包提供了Date类型和DateTime类型。
日期不包含时区,
可以调用TimeZones包的功能来支持时区。
日期
Dates.today()
返回当天日期。
可以从年月日的整数值用Dates.Date()
转换成日期,
也可以从日期字符串按照某个模板转换成日期。
如:
Dates.Date(2018)
## 2018-01-01
Dates.Date(2018, 10)
## 2018-10-01
Dates.Date(2018, 10, 31)
## 2018-10-31
Dates.Date("2018-10-31")
## 2018-10-31
Dates.Date("2018-10-31", "y-m-d")
## 2018-10-31
Dates.Date("20181031", "yyyymmdd")
## 2018-10-31
Dates.Date.([2018, 2018], [3, 10], [15, 31])
2-element Array{Dates.Date,1}:
2018-03-15
2018-10-31
Dates.Date.(["20180315", "20181031"], "yyyymmdd")
2-element Array{Dates.Date,1}:
2018-03-15
2018-10-31
日期时间
可以用DateTime()
函数将年、月、日、时、分、秒、毫秒整数值转换成日期时间,
精确到1毫秒。
如
Dates.DateTime(2018, 10, 31)
## 2018-10-31T00:00:00
Dates.DateTime(2018, 10, 31, 12, 15, 30)
## 2018-10-31T12:15:30
Dates.DateTime(2018, 10, 31, 12, 15, 30, 136)
## 2018-10-31T12:15:30.136
提取成分
- 用
Dates.year(d)
提取年,Dates.month(d)
提取月份数值,
Dates.day(d)
提取日数值。 - 用
Dates.yearmonth(d)
提取年、月元组,
Dates.monthday(d)
提取月、日元组,
Dates.yearmonthday(d)
提取年、月、日元组。 - 用
Dates.dayofweek(d)
提取星期几的数值,
星期一返回1,星期日返回7。 - 用
Dates.dayname(d)
返回星期几的名称,如"Monday"
。 - 用
Dates.dayofmonth(d)
返回当前的星期号码是本月的第几个。
日期运算
两个日期或者时间可以比较大小,
可以相减。结果带有单位,
用Dates.value()
转换为表示天数或者毫秒数的整数值。
如
Dates.Date(2018, 10, 31) - Dates.Date(2018)
## 303 days
Dates.Date(2018, 10, 31) - Dates.Date(2018) |> Dates.value
## 303
Dates.DateTime(2018, 10, 31, 12, 15, 30, 136) - Dates.DateTime(2018)
## 26223330136 milliseconds
Dates.DateTime(2018, 10, 31, 12, 15, 30, 136) -
Dates.DateTime(2018) |> Dates.value
## 26223330136
Dates.DateTime(2018, 10, 31, 12, 15, 30, 136) -
Dates.DateTime(2018) |>
Dates.value |>
x -> x / (24*3600*1000)
## 303.510765462963
日期和日期时间不能直接加减数值,
而需要用单位表示,类型为Dates.Period
。
比如,
加一天应该加Dates.Day(1)
;
单位包括:
Dates.Year()
Dates.Month()
Dates.Day()
Dates.Hour()
Dates.Minute()
Dates.Second()
Dates.Millisecond()
: 毫秒。
如:
Dates.Date("2020-01-01", "y-m-d") +
Dates.Year(2) + Dates.Month(6) + Dates.Day(15)
## 2022-07-16
日期序列
可以用start:step:end
格式生成一系列日期,
其中step
用日期单位。
如:
Dates.Date("2020-01-01"):Dates.Day(1):Dates.Date("2020-01-07")
## Dates.Date("2020-01-01"):Dates.Day(1):Dates.Date("2020-01-07")
Dates.Date("2020-01-01"):Dates.Day(1):Dates.Date("2020-01-07") |> collect
7-element Vector{Dates.Date}:
2020-01-01
2020-01-02
2020-01-03
2020-01-04
2020-01-05
2020-01-06
2020-01-07
使用DataFramesMeta包
DataFramesMeta包实现了与R的dplyr的类似功能,
能查询、排序、计算新变量、计算汇总统计量、分组汇总。
主要好处是可以直接使用符号形式的变量名,
以及可以用“@chain
”写出链式的查询。
与DataFrames的transform
, combine
, groupby
等函数相比,
DataFramesMeta的做法更简洁。
参见:
- https://github.com/JuliaData/DataFramesMeta.jl
查询、排序、计算新变量
using DataFrames, DataFramesMeta
@chain d_class begin
@subset(:sex .== "M")
@transform(:bmi = :weight ./ (:height ./ 100.0).^2)
@select(:name, :years = :age, :bmi)
@orderby(:name)
end
5 rows × 3 columns
name | years | bmi | |
---|---|---|---|
String | Int64 | Float64 | |
1 | James | 12 | 17.827 |
2 | Jeffrey | 13 | 15.0311 |
3 | John | 12 | 20.0 |
4 | Robert | 12 | 21.3039 |
5 | Thomas | 11 | 18.2961 |
- 用
@chain
指定进行一系列的数据框操作,
用begin
和end
界定。 - 用
@subset
取行子集,
注意关于列向量的比较运算需要使用加点的广播形式。
多个条件用逗号分隔。 - 用
@transform
计算新变量,
注意使用列向量进行计算时要用广播形式。
多个新变量定义用逗号分隔。 - 用
@select
选择列子集,
并且可以用新变量名=老变量名
的格式改名。 - 用
@orderby
排序。
可以使用多个变量。
如果某个变量x
需要降序排列,
用sortperm(:x, rev=true)
。
在系列操作中末尾可以有一般的函数调用,
该函数以系列末尾的结果为第一自变量。
比如,
上述程序末尾的end
之前添加first(3)
行,
将仅显示结果的前3行。
用@combine
汇总计算
用法如:
@chain d_class begin
@combine(:n = length(:height),
:mean = mean(:height),
:std = std(:height))
end
1 rows × 3 columns
n | mean | std | |
---|---|---|---|
Int64 | Float64 | Float64 | |
1 | 9 | 148.333 | 10.0125 |
用groupby
和@combine
汇总计算
@chain d_class begin
groupby(:sex)
@combine(:n = length(:height),
:mean = mean(:height),
:std = std(:height))
end
2 rows × 4 columns
sex | n | mean | std | |
---|---|---|---|---|
String | Int64 | Float64 | Float64 | |
1 | F | 4 | 142.25 | 9.10586 |
2 | M | 5 | 153.2 | 8.46759 |
分组后也可以做变换。
比如,按性别分组,每组的身高减去本组的最小值,
使用@transform
:
@chain d_class begin
groupby(:sex)
@transform(:hextra = :height .- minimum(:height))
@orderby(:sex, :hextra)
end
9 rows × 6 columns
name | sex | age | height | weight | hextra | |
---|---|---|---|---|---|---|
String | String | Int64 | Float64 | Float64 | Float64 | |
1 | Sandy | F | 11 | 130.0 | 23.0 | 0.0 |
2 | Karen | F | 12 | 143.0 | 35.0 | 13.0 |
3 | Alice | F | 13 | 144.0 | 38.0 | 14.0 |
4 | Kathy | F | 12 | 152.0 | 38.0 | 22.0 |
5 | Thomas | M | 11 | 146.0 | 39.0 | 0.0 |
6 | James | M | 12 | 146.0 | 38.0 | 0.0 |
7 | John | M | 12 | 150.0 | 45.0 | 4.0 |
8 | Jeffrey | M | 13 | 159.0 | 38.0 | 13.0 |
9 | Robert | M | 12 | 165.0 | 58.0 | 19.0 |
用Query包进行查询
利用Query包可以实现从各种数据源的信息查询,
这也包括从数据框查询,
语法参照了C#语言的LINQ工具。
参见:
- https://github.com/queryverse/Query.jl
Query查询以@from
宏开始,
由若干个查询命令连接而成,@from
宏可以指定行遍历,@where
宏指定行筛选条件,@select
宏可以指定列子集并为结果列变量改名,@orderby
宏指定排序方式,@collect
宏使行遍历结果组合为数据框输出。
Query和DataFramesMeta都定义了@select
和@orderby
,
所以不适合在同一程序中或同一会话同时使用。
从d_class
数据框中查询所有性别为女、年龄在12岁及以下的人的姓名、体重,
按体重排序:
using Query
df_tmp = @from i in d_class begin
@where i.sex=="F" && i.age <= 12
@select {i.name, 体重=i.weight}
@orderby 体重
@collect DataFrame
end
name | 体重 |
---|---|
“Sandy” | 23.0 |
“Karen” | 35.0 |
“Kathy” | 38.0 |
查询的结果保存成了一个数据框。
如果省略@collect
命令,
结果会是一个Julia迭代器,
可以用在for
循环中遍历各行。
分组汇总
对数据框经常需要按某一个或几个分类变量进行分类汇总。
可以将数据框分成若干个子数据框,
对每一自数据进行一些汇总处理,
然后将汇总结果合并为一个数据框,
这样的流程称为“分组-操作-合并”(Split-Apply-Combine)。
用groupby()
函数分组。
分组后,
可以用combine()
进行汇总统计然后合并结果;
可以用select
, select!
生成与每个子数据框行数相同的仅包含新生成列的结果;
可以用transform
, transform!
对每个子数据框生成与每个子数据框行数相同的结果数据框,
其中包括原有列与新生成的列。
最后将分组的结果合并。
对每个子数据框汇总或者变换时,
用“cols => func
”或格式“cols => func => newcols
”格式指定那些了列需要进行什么操作。
实际上,
部分组也可以使用这些函数进行操作,
这可以看成是仅有一个组。
例如,
将分组结果保存为一个变量,
后续可以对利用此变量进行多种分组汇总或变换。
先求各组的观测数:
using DataFrames, Statistics
dcl = copy(d_class)
gdf = groupby(d_class, :sex)
combine(gdf, nrow)
2 rows × 2 columns
sex | nrow | |
---|---|---|
String | Int64 | |
1 | F | 4 |
2 | M | 5 |
如果不需要对分组进行多种汇总,
也可以用“|>
”写成链式调用:
dcl |>
df -> groupby(df, :sex) |>
subdf -> combine(subdf, nrow)
2 rows × 2 columns
sex | nrow | |
---|---|---|
String | Int64 | |
1 | F | 4 |
2 | M | 5 |
对每组求平均身高:
dcl |>
df -> groupby(df, :sex) |>
subdf -> combine(subdf, :height => mean)
2 rows × 2 columns
sex | height_mean | |
---|---|---|
String | Float64 | |
1 | F | 142.25 |
2 | M | 153.2 |
在对每组汇总时,用“cols => function
”的格式指定对某些列使用那些汇总,
结果数据框的统计量自动命名,
也可以用“cols => function => target_col
”指定结果列名,如:
dcl |>
df -> groupby(df, :sex) |>
subdf -> combine(subdf, :height => mean => :均值)
2 rows × 2 columns
sex | 均值 | |
---|---|---|
String | Float64 | |
1 | F | 142.25 |
2 | M | 153.2 |
在combine
中指定多个统计函数:
dcl |>
df -> groupby(df, :sex) |>
subdf -> combine(subdf,
nrow => :观测数,
:height => mean => :平均身高,
:weight => mean => :平均体重)
2 rows × 4 columns
sex | 观测数 | 平均身高 | 平均体重 | |
---|---|---|---|---|
String | Int64 | Float64 | Float64 | |
1 | F | 4 | 142.25 | 33.5 |
2 | M | 5 | 153.2 | 43.6 |
也可以利用多列计算一个统计量,
如两列的相关系数:
dcl |>
df -> groupby(df, :sex) |>
subdf -> combine(subdf, [:height, :weight] => cor => :corr)
2 rows × 2 columns
sex | corr | |
---|---|---|
String | Float64 | |
1 | F | 0.930356 |
2 | M | 0.711767 |
如果函数要返回多列结果,
就需要用“cols => func => AsTable
”模式。
例如:
combine(gdf, :height => (x -> (mean = mean(x), std = std(x))) => AsTable )
2 rows × 3 columns
sex | mean | std | |
---|---|---|---|
String | Float64 | Float64 | |
1 | F | 142.25 | 9.10586 |
2 | M | 153.2 | 8.46759 |
如果一个统计函数得到多个结果,
比如Statistics.extrema
返回最小值和最大值,
只要将返回结果包装成一个向量,
并对应到不同变量名,如:
combine(gdf, :height => (x -> [extrema(x)]) => [:min, :max])
2 rows × 3 columns
sex | min | max | |
---|---|---|---|
String | Float64 | Float64 | |
1 | F | 130.0 | 152.0 |
2 | M | 146.0 | 165.0 |
可以利用多列计算,得到多个结果,如:
combine(gdf, [:height, :weight] =>
((x, y) -> (m1 = mean(x), m2=mean(y), cor = cor(x,y))) => AsTable)
2 rows × 4 columns
sex | m1 | m2 | cor | |
---|---|---|---|---|
String | Float64 | Float64 | Float64 | |
1 | F | 142.25 | 33.5 | 0.930356 |
2 | M | 153.2 | 43.6 | 0.711767 |
在指定列时,仍可以使用Cols()
, Not()
, Between()
, All()
这些函数。
也可以用一个子数据框作为输入,
写成combine(subdf -> func, gdf)
的形式,如:
combine(subdf -> (
meanrat = mean(subdf.height)/mean(subdf.weight),
stdrat = std(subdf.height)/std(subdf.weight)),
gdf)
2 rows × 3 columns
sex | meanrat | stdrat | |
---|---|---|---|
String | Float64 | Float64 | |
1 | F | 4.24627 | 1.27508 |
2 | M | 3.51376 | 0.989026 |
数据框的其它功能
DataFrames包中还有许多函数,详见其文档的函数部分。
可用于DataFrame类型的函数包括(未特别说明都属于DataFrames包):
size(df)
返回行数和列数,size(df, 1)
返回行数,size(df, 2)
返回列数:
size(d_class), size(d_class, 1), size(d_class, 2)
((9, 5), 9, 5)
eltype.(eachcol(df))
获得每列的数据类型:
eltype.(eachcol(d_class))
5-element Array{DataType,1}:
String
String
Int64
Float64
Float64
first(df, k)
, last(df, k)
返回数据框头部或者尾部的若干行:
3 rows × 5 columns
name | sex | age | height | weight | |
---|---|---|---|---|---|
String | String | Int64 | Float64 | Float64 | |
1 | Sandy | F | 11 | 130.0 | 23.0 |
2 | Karen | F | 12 | 143.0 | 35.0 |
3 | Kathy | F | 12 | 152.0 | 38.0 |
3 rows × 5 columns
name | sex | age | height | weight | |
---|---|---|---|---|---|
String | String | Int64 | Float64 | Float64 | |
1 | John | M | 12 | 150.0 | 45.0 |
2 | Robert | M | 12 | 165.0 | 58.0 |
3 | Jeffrey | M | 13 | 159.0 | 38.0 |
用eachcol(df)
提供对每一列的遍历,
如:
import Statistics.mean
[mean(y) for y in eachcol(d_class[!, [:age, :height, :weight]])]
3-element Array{Float64,1}:
12.0
148.33333333333334
39.111111111111114
用select!
选择仅保留指定的列, 如:
dtmp = copy(d_class)
select!(dtmp, [:age, :height, :weight])
names(dtmp)
3-element Array{String,1}:
"age"
"height"
"weight"
为了删除指定的列,可以在select!
中用Not()
指定若干列,如:
dtmp = copy(d_class)
select!(dtmp, Not([:age, :height, :weight]))
names(dtmp)
2-element Array{String,1}:
"name"
"sex"
用rename!
修改变量名,如:
dtmp = copy(d_class)
rename!(dtmp, :age => :years, :height => :h)
names(dtmp)
5-element Array{String,1}:
"name"
"sex"
"years"
"h"
"weight"
用filter(f, df)
筛选行,
其中f
是以行作为元组类型自变量的示性函数。
如:
dtmp = copy(d_class)
filter(row -> row.sex == "F", dtmp)
4 rows × 5 columns
name | sex | age | height | weight | |
---|---|---|---|---|---|
String | String | Int64 | Float64 | Float64 | |
1 | Sandy | F | 11 | 130.0 | 23.0 |
2 | Karen | F | 12 | 143.0 | 35.0 |
3 | Kathy | F | 12 | 152.0 | 38.0 |
4 | Alice | F | 13 | 144.0 | 38.0 |
其它一些函数:
- Base中的
filter
,filter!
,join
,similar
,sort
,sort!
,unique
aggregate
allowmissing!
,disallowmissing!
调整列使得其允许或不允许包含缺失值by
将观测分组汇总combine
,groupby
可以将数据框分组处理后合并处理结果completecases
,dropmissing
,dropmissing!
用于获得完全观测eachrow
用来逐行遍历数据框eltype.(eachcol(df))
获得每列的数据类型melt
,meltdf
,stack
,stackdf
用于将宽表变成长表,unstack
用于将长表变成宽表names!
为数据框修改所有变量名unique
,unique!
,nonunique
用于处理重复行或重复的变量值组合describe
对每列进行简单描述统计
韭菜热线原创版权所有,发布者:风生水起,转载请注明出处:https://www.9crx.com/75105.html