Introduction to DS - R 语言初探之壹
COMP2501 的 Ting 老教授完美符合我对上了年纪的老师的印象:佛系,人好,但上课催眠;加之 R 语言更近似于一种脚本型语言,semantics 相对简单 (甚至绝大部分操作都是在 REPL 中进行的),遂决定自学。
This article is a self-administered course note.
It will NOT cover any exam or assignment related content.
The Very Basics
Objects. We use the term object to describe stuff that is stored in R. Variables are examples, but objects can also be more complicated entities such as functions.
1 | a <- 1 |
ls()
function displays all objects in the environment (workspace).- use
rm(a)
to remove objecta
from current environment.
Built-in Functions.
1 | args(log) |
- use
help("log")
or?log
to display manual page for functionlog()
. - \(=\) v.s. \(\leftarrow\).
=
is to specify arguments,<-
is to assign values to variables. When arguments' names are used, the order is no longer important.
Data Types
Basic Data Types
- numeric. e.g.,
a <- 1
. - integer (subset of numeric). e.g.,
b <- 1L
. - character. e.g.,
c <- "hello"
. - logical. e.g.,
d <- TRUE
. (capitalizedTRUE
&FALSE
)
Data Frames
The most common way of storing a dataset in R is in a data frame. Think of it as a table:
- rows: observations.
- columns: different variables reported for each observation.
1 | library(dslabs) |
library()
可理解为import
(Java, Python) 或include
(C).data(murders)
将数据集murders
导入当前的环境中。str()
function is useful for finding out more about the structure of an object.
1 | names(murders) |
names()
函数返回 data frame 的所有 variables' (columns) names.[data_frame_name]$[variable_name]
: 使用 accessor$
取出对应的 column。
Vectors
The object murder$population
is not one number but
several. We call these types of objects vectors.
1 | pop <- murder$population |
注意到这里有一个 counter-intuitive 的细节:vector pop
的类型是 numeric
,而不是某个类似
numeric vector
或 numeric[]
的东西;这是因为在
R 中,a single number is technically a vector of length \(1\).
由此可见,与我们之前接触过的其他语言不同,在 R 语言中 the most basic objects available to store data are vectors. 这样的规定带有很强烈的 data wrangling 色彩:在进行数据处理时,最基本的单位并非某个单独的数据单元,而是一组 (通常情况下) 具有相同特征的数据集。
1 | z <- 3 == 2 |
We shall discuss vectors in full detail later.
Factors
当 data frame 的某一列中 distinct elements 的数量较少时,我们可以使用
factors 来节省空间。举例来说,murders
的
region
列中只有四种元素:Northeast, South, North Central 与
West。
1 | class(murders$region) |
We use levels()
to display all levels of a
certain factor object.
- In the background, R stores these levels as integers and keeps a map to keep track of the labels. This is more memory-efficient than storing all the characters.
- The default in R is for the levels to follow alphabetical order.
The function reorder()
lets us change the order of the
levels of a factor variable (e.g., region
) based on a
summary computed on a numertic vector (e.g., value
):
1 | region <- murders$region |
The above code takes the sum of the total murders in each region, and
reorders the factor following these sums. The new order shows that
Northeast
has the least murders while South
has the most.
Lists
Data frames are special case of lists. (data frame 本质上是一个含有若干长度相同的 vectors 或 factors 的 list) Lists are useful because you can store any combination of different types (and vectors can't).
1 | record <- list(name = "John Doe", |
与在 data frame 中一致,我们使用
[list_name]$[variable_name]
来访问 list 中对应的
field。除此之外,[[]]
+ "name"
也能达到同样的目的。有时我们会遇到 lists without names:
1 | record2 <- list("John Doe", 1234) |
我们可以把这种 lists 视作能够储存不同类型数据的 vectors。由于这种
lists 的 fields 是匿名的,我们无法通过 $
+ name
进行访问。Instead,我们使用 [[]]
+ index
来访问对应下标的元素。
注意 R 语言遵循 1-indexed rule: 下标从 1 而非从 0 开始!
Matrices
- Matrices are two-dimensional (like data frames).
- All entries in matrices have to be all the same type (like vectors).
Compared to matrices, data frames are much more useful for storing data. Yet matrices have a major advantage over data frames: we can perform matrix algebra operations on them.
使用 matrix()
函数创建一个新的 matrix;使用
as.data.frame()
函数方便的将 matrix 转化为 data frame。
1 | mat <- matrix(1:12, 4, 3) |
使用 []
,我们可以随心所欲地进行 matrix
slicing。若被切割的是 matrix 中的某「条」(one-demensional)
数据,返回的将是一个 vector;若是某「块」(two-dimensional)
数据,返回的将是一个 matrix。
1 | mat[2, 3] |
此外,相似的 slicing 方法也可以用在 data frame 中:
1 | data("murders") |
Vectors
Creating Vectors
We can create vectors using the function c()
, which
stands for concatenate.
1 | grades <- c(95, 82, 91, 97, 93) |
与 lists 不同,vectors 中所有 entries
的类型必须是一致的。当我们尝试使用 c()
创建一个含有不同类型元素的 vectors 时,lower-level types (e.g., numeric)
将转化为 higher-level types (e.g., character)。
1 | grades <- c(95, 82, "91", "97", "93") |
Names
与 lists 一样,我们可以为 vectors 中的 entries 命名,并对具名的
vectors 调用 names()
函数。
1 | codes <- c(italy = 380, canada = 124, egypt = 818, 86) |
在为 vectors 的 entries 命名时将变量名用引号 ""
括起来
makes no difference。此外,我们还能使用 names()
函数来
assign names - 即,names can be given later after being defined.
1 | codes <- c(380, 124, 818) |
Sequences
使用 sequences 来迅速创建一个元素符合特定规律的 vector。Python 中也有类似的 idiom,很方便。
1 | seq(1, 10) |
只有当未指定 jump length 时 (即使用默认的 jump length
1),seq()
函数返回的 vector 类型才是 integer;其余情况下
seq()
函数返回的 vector 都是 numeric 类型的。
seq(a, b)
与 a:b
这一表达等价:但
a:b
这种表达只能生成 jump length 为 1 的 vectors。
1 | 1:10 |
Subsetting
我们使用 single square brackets []
+ name/index 来访问
vector 中的 entry。神奇的是,You can get more than one entry by using a
multi-entry vector referring to indexes/names.
1 | codes[2] |
Rescaling
In R, arithmetic operations on vectors occur element-wise.
1 | a <- c(0.01, 0.02, 0.03, 0.04, 0.05) |
Two Vectors
Two vectors a
, b
:
- 等长:
c <- a + b
意味着 \(c_i=a_i+b_i\). - 不等长:出现 recycle;较短的 vector 发生 recycle 直至两 vectors 等长 \(\to\) 第一种情况。
1 | x <- c(1, 2, 3) |
Coercion
coercion is an attempt by R to be flexible with data types. When an entry does not match expected type, some of the prebuilt R functions try to guess what was meant before throwing errors.
R 以放弃 type safeness 为代价换取了 type flexiblity. 几个 coercion 的例子 (有些我们已经接触过了):
- when coercion is possible: R coerced lower-level data into higher-level data.
- when coercion is impossible: Not availables (NA) is introduced.
1 | x <- c(1, "canada", 3) |
以上是 possible coercion 的例子:R 为了遵循「vector 内类型一致」这一规则,将 numeric 类型的数据强制转换为了 character 类型。
1 | x <- c("1", "b", "3") |
当某种强制类型转换不可能完成时 (impossible coercion: 我们无法将一个
character 类型的数据转化为 numeric 型),伴随着一个轻飘飘的 warning
信息,NA
这一特殊的值将被引入。
Sorting
data wrangling 当然是离不开 sorting 的;接下来我们来介绍一下 R 中提供了哪些方便的 sorting features。
sort
The function sort()
sorts a vector in increasing order;
but it does not give us any additional information.
1 | library(dslabs) |
order
The function order()
returns the vector of
indexes that sorts the input vector.
1 | x |
由于 R 中 vector 的 index 可以是一个 multi-entry
vector,我们通过这种方式来实现 sort()
。order()
能做到的不仅如此:If we want to order the states by murders,
sort()
alone can not accomplish the task.
1 | ind <- order(murders$total) |
- First obtain the index that orders the vectors according to murder totals.
- Then index the state names vector.
由此可见,加州 CA
的 total murders 数量最多,佛蒙特州
VT
数量最少。
max
&
which.max
分别返回最大值的 value 与 index。同理还有 min()
与
which.min()
。
1 | max(murders$total) |
rank
rank()
函数返回给定 vector 中所有 entries 的排名。
1 | x <- c(31, 4, 15, 92, 65) |
Indexing
R provides a powerful and convenient way of indexing vector. We can,
for example, subset a vector based on properties of another vector. In
expression a[b]
, b
could be:
- vector of indexes. [见 5.2 order 一节]
- logical vector. [stay tuned]
Subsetting with logicals
很好理解。vector a
与 logical vector b
;
a[b]
means:
- 等长:\(a_i\) remains only when \(b_i=\mathtt{TRUE}\).
a
的长度大于b
:b
发生 recycle,与a
等长后 \(\to\) 第一种情况。a
的长度小于b
: 超出的部分将引入NA
.
下面来看看我们是如何通过 logical subsetting 找到所有 murder rate 小于 0.71 的州的:
1 | murder_rate <- murders$total / murders$population * 100000 |
Logical operators
In R, both &
and &&
are logical
AND but not bitwise AND. But always use &
since
&&
assumes length 1 on input and only compares
once.
下面这段代码能够找到所有 murder rate 小于等于 1 的西部州。
1 | west <- murders$region == "West" |
which
which()
函数能够返回给定 logical vector 中所有值为
TRUE
的 entries 的 indexes。因此,对于某个 logical vector
b
, a[b]
与 a[which(b)]
得到的结果通常是相同的。(logical subsetting v.s. index subsetting)
假设我们想要查找加利福尼亚州的 murder rate:
1 | logical_ind <- murders$state == "California" |
match
假设我们想要同时查找不止一个州的 murder rate;此时可以使用函数
match()
。
1 | match(c("New York", "Florida", "Taxas"), murders$state) |
但其实使用 logical vectors 之间的逻辑或 |
也能达成目的,只是略显臃肿:
1 | which(murders$state == "New York" | murders$state == "Florida" | murders$state == "Texas") |
%in%
If rather than an index we want a logical that tells us whether or
not each element of a first vector is in a second, we can use the
function %in%
.
1 | c("Boston", "Dakota", "Washington") %in% murder$state |
通过 which()
,我们能将 match()
(index-based) 与 %in%
(logic-based) 联系起来。
1 | match(c("New York", "Florida", "Texas"), murder$state) |
Basic plots
R 语言的一大特色就是其能够以简单的语法快速生成图像。(见 MIT-Data Wrangling 一节)
plot
使用 plot()
函数来绘制 scatterplot (散点图)。
1 | x <- murders$population / 10^6 |
with()
function allows us to use the
murders
column names in the plot()
function.
1 | with(murders, plot(population, total)) |
plot(x, y)
函数有两个参数:散点图描述变量 x
与 y
间的关联或分布模式。
hist
使用 hist()
函数来绘制 histogram (直方图)。
1 | x <- with(murders, total/population * 100000) |
hist(x)
函数只有一个参数:直方图描述变量 x
的频率分布情况。
boxplot
使用 boxplot()
函数来绘制 boxplot (箱型图)。
1 | murders$rate <- with(murders, total/population * 100000) |
Boxplot demonstrating the locality, spread and skewness groups of numerical data.
Programming Basics
到目前为止介绍的所有内容都可以在 REPL 中复现;我们藉此强调 R 语言的 interactive 特征与其数据处理方面的特化。但 R 同样是一种 programming language,它当然也具有成熟的流程控制系统。
if-else statement
常见的 if
-else
逻辑语句。
1 | library(dslabs) |
R 语言还有一个 built-in function for if-else:
ifelse(a, b, c)
. 可以将其视为 C++ 中的三目运算符
a?b:c
。The function is particularly useful because it works
on vectors (联想到 higher-order function).
1 | a <- c(0, 1, 2, -4, 5) |
再来看一个例子:我们使用 ifelse()
函数将某个 vector
na_example
中的缺失值 (即 NA
) 全部替换为
0。
1 | data(na_example) |
any
& all
很经典的函数了。any()
与 all()
函数均作用于某个 logical vector。
any(a)
: 结果为 \(a_1\lor a_2 \lor a_3...a_{n-1}\lor a_n\).all(a)
: 结果为 \(a_1\land a_2 \land a_3...a_{n-1}\land a_n\).
Defining functions
无需多言。R 中的函数定义语法与大多数主流语言一致。
学到这里应该能隐约察觉到了:虽然 R 中最基本的存储单元被称为 object,但它显然与 OOP 中的 object 关系不大;R 本质上来说是一种 functional programming language。
以下函数根据传入的参数 arithmetic
选择计算
x
的算数平均值或几何平均值。
1 | avg <- function(x, arithmetic = TRUE) { |
function
关键字声明一个函数将被定义。注意到这里我们使用
<-
赋值符号将函数赋给了名为 avg
的变量。
仔细体会一下,这个写法其实非常的 functional programming:它暗示着在 R 中,函数与任何 object 一样,都是立派な一等公民,能被储存与传递。
For-loops
同样没什么需要强调的。下面的例子通过循环语句计算数列 \(S_n\);其中 \(S_i=\sum_{1}^{i}i\).
1 | compute_s_n <- function(n) { |
Vectorizations & Functionals
Although for-loops are an important concept to understand, in R we rarely use them. That's because most functions in R are vectorized.
不禁联想到在 Programming Languages 这门课中所学的:higher-order functions 的存在是 functional programming 的重要特征之一,而这类函数所实现的功能就是我们这里所提到的 vectorization。
某些 OOP 语言例如 Ruby,通过引入 blocks 的概念也能够较为轻松的实现类似 vectorization 的功能;我们能够看到,在支持 vectorization 的语言中,循环控制语句被使用的频率远低于不支持的语言。
R 提供了一个 general high-order function 的接口,我们称其为
functionals;sapply()
函数是其中之一。
回到之前计算数列 \(S_n\) 的例子:如何使用 vectorization & functionals 而不是循环控制语句进行实现?
1 | n <- 1:25 |
可以看到,与 for-loop 相比,vectorization 的实现显然更加简洁优雅。这也就能解释为什么像 Ruby 这样的 OOP 语言也想要引入类似的 features。
Reference
This article is a self-administered course note.
References in the article are from corresponding course materials if not specified.
Course info:
Code: COMP2501, Lecturer: Dr. H.F. Ting.
Course textbook:
Data Analysis and Prediction Algorithms with R - Rafael A. Irizarry.