一. 前言
查看已经命名了哪些 R 对象
ls()
四舍五入
round()
阶乘
factorial()
平均数
mean()
从向量 x 中抽取 size 个元素并返回
sample(x= , size=)
查看函数的所有参数名
args()
求和
sum()
构建函数,res 为构建出的函数名
res <- function(bones){
sum(sample(bones , 2 , TRUE))
}
清除变量
rm()
rm(list=ls())
安装包
install.packages("ggplot2")
加载包,新会话需要重新加载
library("ggplot2")
重复运行 R 代码
replicate(10 , roll()) #运行 10 次 roll 函数
测试某个对象是否是原子型向量
is.vector()
返回原子型向量的长度
length()
查看某对象的类型
typeof()
明确设定整形: L
int <- c(1L , 2L , 3L)
typeof(int)
查看一个对象包含哪些属性信息
attributes()
查看一个对象的“名称”属性
name()
将“名称”属性赋值给一个对象
name(die) <- c("one","two","three","four","five","six",)
一次性删除“名称”属性值
name(die) <- NULL
查看一个对象的“维度”属性
dim()
将“名称”属性赋值给一个对象
dim(die) <- c(2 , 3)
矩阵,默认列优先
m <- matrix(die , nrow=2)
矩阵,行优先
m <- matrix(die , nrow=2 , byrow=TRUE)
数组
ar <- array(c(11:14 , 21:24 , 31:34) , dim=c(2 , 2 , 3))
更改对象的维度并不会改变其类型,但是会改变这个对象的 class 属性
查看一个对象的“class”属性
class()
查看计算机的当前时间
Sys.time()
移除对象的 class 属性
unclass()
将“class”属性赋值给一个对象
mil <- 1000000
class(mil) <- c("POSIXct" , "POSIXt")
mil
生成一个因子
gender <- factor(c("male" , "female" , "female" , "male"))
将一个因子强制转换成字符串
as.character()
原子型向量只能存储一种类型的数据
card <- c("ace" , "hearts" , 1)
card
如果向量中有字符型元素,那么所有其它元素就会被强制转换成字符型数据;如果向量中只有数值型和逻辑型,那么逻辑型会被强制转换为数值型
强制转换为逻辑值
as.logical(1)
强制转换为数值型
as.numerical(FALSE)
向量,矩阵,数组只能存储单一数据类型;列表可以组织 R 对象
创建列表
list_1 <- list(100:130 , "R" , list(TRUE , FALSE))
数据框:二维表格,每列存储一种类型的数据,列与列之间的数据类型可以不同,数据框中的每一列都必须具有相同的长度
创建数据框
df <- data.frame(
face = c("ace" , "two" , "six"),
suit = c("clubs" , "clubs" , "clubs"),
value = c(1 , 2 , 3)
)
查看对象是怎样组织的
str()
检视数据集的前6行,可以指定行数
head()
检视数据集的后6行,可以指定行数
tail()
二. 索引
正整数索引
# 索引从 1 开始
vector[1]
vector[1:3]
dataframe[1 , 2]
dataframe[1 , c(2:3)]
#重复某个值,会重复提取相应的数据
dataframe[c(1 , 1) , c(1 , 2 , 3)]
dataframe[1:2 , 2:3]
dataframe[1:2 , 1]
#如果只提取一列, R会返回一个向量,如果仍想返回一个数据框,添加参数 drop=FALSE
dataframe[1:2 , 1 , drop=FALSE]
负整数索引
# 在索引时使用负整数的效果与正整数刚好相反。R 返回的元素将不包含负整数索引所对应的元素
dataframe[-(2:52) , 1:3]
dataframe[-1:1]
0索引
# 说实话,没有多大用处
dataframe[0 , 0]
空格索引
# 提取该索引位置所代表维度的所有元素
dataframe[1 ,]
逻辑值索引
# 匹配索引值为 TRUE 的位置,忽略索引值为 FALSE 的位置
dataframe[1 , c(TRUE , TRUE , FALSE)]
名称索引
dataframe[1 , c("face" , "suit" , "value")]
dataframe[,"value"]
美元符号
# 使用 $ 分隔数据框的名称与想要提取的列的名称
dataframe$value
# $取列表不再是一个列表,而是对应的数据类型
lst <- list(numbers=c(1,2) , logical=TRUE , strings=c("a" , "b" , "c"))
lst$numbers
双中括号
# 如果列表中的元素没有名称(或者你不想使用名称),那么可以使用双中括号取子集,效果等同于 $
lst <- list(numbers=c(1,2) , logical=TRUE , strings=c("a" , "b" , "c"))
lst[[1]]
想像一个列表对象就是一列火车,而列表中的每一个元素都是一节车厢。当你使用单中括号提取元素时,R会将某一节车厢提取出来,再配上火车头,使其成为一列新的火车。每一节车厢都完整保存了其完整的数值内容,但这些内容仍然处于车厢内部。而当你使用双中括号时,R会实实在在地卸下这节车厢,将其中的内容返回给你。
三. 对象取值
vec <- c(0 , 0 , 0 , 0)
vec[1] <- 1000
vec[c(1 , 2 , 3)] <- c(1 , 1 , 1)
vec[1:3] <- vec[1:3] + 1
vec[] <- vec[] + 1
# 可以创建一个原先对象中并不存在的新值。R 会自动将对象的长度延申以适应这个新值
vec[5] <- 0
# 上面提供了一种为数据集添加新变量的绝佳方法
dataframe$new <- 1:52
dataframe$new <- NULL
dataframe$value[c(13 , 26 , 39 , 52)] <- 14
逻辑运算符
# > , >= , < , <= , == , != , %in%
# 这些运算符应用于向量间的判别,R会将两个元素中的每个元素一一进行对比
1>2
1>c(0 , 1 , 2)
c(1 , 2 , 3) == c(3 , 2 , 1)
1 %in% c(3 , 4 , 5)
c(1 , 2) %in% c(3 , 4 , 5)
c(1 , 2 , 3) %in% c(3 , 4 , 5)
c(1 , 2 , 3 , 4) %in% c(3 , 4 , 5)
# 逻辑值取子集对于向量化编程来说至关重要
dataframe$value[dataframe$face == "ace"] <- 14
dataframe[dataframe$value == "queen" , ]
布尔运算符
# & , | , xor , ! , any , all
df$face=="queen" & df$suit =="spades"
缺失信息
# 特殊字符 NA 代表 “不可用”
1 + NA
NA == 1
c(NA , 1:50)
mean(c(NA , 1:50))
# 大多数 R 函数都有一个可选参数 na.rm,他代表将 NA 移除
mean(c(NA , 1:50) , na.rm=TRUE)
# 测试某个值是否是一个缺失值
is.na(NA)
四. R 的环境系统
查看 R 的环境系统
library(pryr)
parenvs(all=TRUE)
指向环境树中的任意一个环境
as.environment("package:stats")
环境树中有三个环境拥有自己的调用函数
# 全局环境,打开 R 会话后创建的所有对象都会存储在全局环境中
globalenv()
# 基环境
baseenv()
# 空环境,是 R 中唯一一个没有父环境的环境
emptyenv()
查看一个环境的父环境
parent.env(globalenv())
查看存储在环境中的对象
ls(emptyenv())
使用 $ 可以提取某个特定环境中的某个特定对象
head(globalenv()$deck , 3)
使用 assign 将对象存储到某个特定环境中
# 首先提供对象的名称(用字符串格式作为输入),然后提供新对象的值,最后再提供想要存储的环境名称
# assign 函数的作用类似于赋值,如果该环境已经存在一个具有相同名称的对象,那么该函数会直接覆盖该对象的值
assign("new" , "hello world" , envir=globalenv())
查看当前的活动环境
# 任何时候,R的活动环境都只有一个。所有的新对象都会存储在该环境中,并且在搜索对象时,也会优先搜索该环境。
# 通常来说,活动环境就是全局环境,但是当你运行函数时,活动环境可能会发生改变
# 在命令行中运行的所有命令都是在全局环境中进行的,因此,在命令行中创建的所有对象都会被存储在全局环境中。你可以将全局环境看作你的 “用户工作区”
environment()
查看一个函数的原环境
environment(show_env)
4.1 作用域规则
在搜索对象时
- 首先在当前活动环境中搜索对象
- 在命令行中工作时,活动环境就是全局环境;因此命令行的所有调用都是发生在全局环境
- 当在某个环境没有搜索到对象时,R 会进入该环境的父环境,然后进入该父环境的父环境,以此类推,直到在某个环境中找到该对象,或者到达环境树的顶层环境(空环境),搜索才停止
R 每次运行函数,都会创建一个新的活动环境,函数的运行是在新环境中进行的
R 会将一个函数的运行时环境与“第一次”创建该函数时所在的环境相连接。所有该函数的运行时环境都会将其作为父环境
如果函数带有参数,R 就会在运行时环境中为每一个参数制作一份副本。
4.2 闭包
setup 的运行环境将 deal 和 shuffle 函数包了起来。deal 和 shuffle 函数都可以直接调用这个包围式环境中的对象,但其它外部的函数几乎都不能做到这一点。这个环境不在任何 R 函数或环境的搜索路径上
setup <- function(deck){
DECK <- deck
DEAL <- function(){
card <- deck[1,]
assign("deck" , deck[-1,] , envir=parent.env(environment()))
card
}
SHUFFLE <- function(){
random <- sample(1:52 , size=52)
assign("deck" , deck[random,] , envir=parent.env(environment()))
}
list(deal = DEAL , shuffle = SHUFFLE)
}
cards = setup(deck)
deal = cards$deal
shuffle = cards$shuffle
五. 程序
将结果输出到控制台窗口
print()
if语句
# 如果 if 语句内的条件是一串向量式的 TRUE 和 FALSE,那么 if 语句只会使用该逻辑向量中的第一个元素,并且会输出一条警告信息。可以使用 any 或 all 将逻辑向量压缩为单个逻辑值
if(){
}
if(){
}
else{
}
if(){
}
else if(){
}
else(){
}
返回一个向量中的所有非重复值
unique()
&& 与 || 在某些场合可以提高代码的效率,但他们不是向量化的运算符
不想保留符号名称
unname()
if 树与查找表
使用 # 在代码中加注释
六. S3
S3 指的是 R 自带的类系统。R 的 S3 系统有三个组成部分:属性(尤其是 class 属性),泛型函数和方法
获取一个对象的行名
row.name()
改变一个对象的行名
row.name(df) = 101:152
levels(df) = c("level 1" , "level 2" , "level 3")
R 允许你为某个对象添加任何你觉得必要的属性(然而大多数属性也会被 R 忽略掉)。只有在某个函数需要找到某个属性却又找不到时,R才会抱怨
为某个对象添加任何属性
# attr 接受两个参数,一个 R 对象和某个属性的名称(以字符串的形式),需要将属性值保存到 attr 的输出结果
attr()
除非你赋予某个属性一个 R 函数能够找到的名称,比如 names 或者 class,否则 R 通常会忽略这个属性
创建带有一组属性的 R 对象
# 第一个参数是一个 R 对象或对象的取值,剩下的参数是你想要添加给这个对象的属性
a_1 = structure(score(symbols) , symbol = symbols)
将若干字符压缩为一个字符串
# c 为你要压缩的字符向量,使用 collapse;a , b 为你要压缩的字符,使用 sep。
# collapse 为字符串向量不同元素之间的分隔符;sep 可以指定如何将不同对象组合成一个字符串
paste(c , collapse="")
paste(a , b , sep="\n$")
# 与 print 相似,然而 cat 不会在输出结果的两侧添加双引号,并且还会将所有的 \n 替换为一个新行或换行符
cat()
查看某泛型函数所支持的方法
methods()
查看 R 中已经存在的针对该类的所有方法
# 注意,如果某个 R 包事先没有经过加载,那么其中的方法就不会出现在 methods 的返回结果中
methods(class = "factor")
为 slots 类属性写一个 S3 型的 print 类方法函数
# 首先赋予对象类属性
class(ob) = slots
# 写 S3 型的 print 类方法函数,函数名称必须是 print.slots,参数必须与 print 函数参数一致
print.slots = function(x , ...){
print("我是S3 型的 print 类方法函数")
}
print(ob)
rm(print.slots)
如果一个对象有很多类属性,UseMethod 会首先寻找并匹配该对象类属性向量中的第一个属性,如果找不到一个对应的类方法,会尝试匹配第二个类属性,以此类推
在 print 函数运行时,如果对象的类没有匹配的 print 方法,那 UseMethod 将会调用一个名为 print.default 的特殊方法,专门用于处理一般情况
6.1 创建一个类
要想创建一个类,应该执行以下操作
- 给类起一个名称
- 给属于该类的每个对象赋 class 属性
- 为属于该类的对象编写常用泛型函数的类方法
要创建一个可靠,运行良好的类,需要做很多工作。通常来说,你需要为 R 中的每一个基本操作编写对应的类方法函数
在尝试编写类方法函数时,你会立即遇到两个挑战
- R 在将多个对象组合成一个向量时会丢弃对象的属性 *(现在好像不会丢弃了 ?)
- R 在对某个对象取子集时也会丢弃其属性(如类属性)*(现在好像也不太一样了)
七. 循环
n 个向量元素的所有组合
rolls = expand.grid(die , die)
rolls$value = rolls$Var1 + rolls$Var2
prob = c("1"=1/6 , "2"=1/6 , "3"=1/6 , "4"=1/6 , "5"=1/6 , "6"=1/6)
roll$prob1 = prob[roll$Var1]
roll$prob2 = prob[roll$Var2]
roll$prob = roll$prob1 * roll$prob2
sum(roll$value * roll$prob)
for 循环
# 对于这个输入空间内的每个值都运行一遍这个代码
for(value in that){
this
}
for(value in c("My" , "Second" , "for" , "loop"))
{
print(value)
}
chars = vector(length=4)
words = c("My" , "fourth" , "for" , "loop")
for(i in 1:4)
{
chars[i] = words[i]
}
while 循环
while(condition)
{
code
}
plays_till_broke = function(start_with)
{
cash = start_with
n = 0
while(cash >= 1)
{
cash = cash - 1 + play()
n = n + 1
}
n
}
repeat 循环
repeat{
}
可以使用 break 终止循环
GetSymbols = function()
{
wheel = c("DD" , "7" , "BBB" , "BB" , "B" , "C" , "0")
sample(wheel , size=3 , replace=TRUE , prob=c(0.03 , 0.03 , 0.06 , 0.1 , 0.25 , 0.01 , 0.52))
}
# 计算奖金,不考虑钻石百搭
Score = function(symbols)
{
# 识别情形
same = length(unique(symbols)) == 1
bars = symbols %in% c("B" , "BB" , "BBB")
# 计算中奖金额
if(same)
{
payouts = c("DD" = 100 , "7" = 80 , "BBB" = 40 , "BB" = 25 , "B" = 10 , "C" = 10 , "0" = 0)
prize = unname(payouts[symbols[1]])
}
else if(all(bars))
{
prize = 5
}
else
{
cherries = sum(symbols == "C")
prize = c(0 , 2 , 5)[cherries+1]
}
# 根据钻石的个数调整中奖金额
diamonds = sum(symbols == "DD")
prize * (2 ^ diamonds)
}
# 格式化展示
print.symbol = function(prize , ...)
{
symbol = attr(prize , "symbol")
symbol = paste(symbol , collapse=" ")
res = paste(symbol , prize , sep="\n$")
cat(res)
}
play = function()
{
symbols = GetSymbols()
prize = structure(Score(symbols) , symbol = symbols , class = "symbol")
}
八. 代码提速
快速的 R 代码经常用到三大法宝:逻辑测试,取子集和元素方式执行;用到这三大法宝的 R 代码往往都具有一种特性:代码是向量化的
# 接受一段 R 表达式输入,运行这段表达式,记录并显示这段代码的运行时长
system.time()
试比较如下两段代码
# 代码1
abs = function(vec)
{
for(i in 1 : length(vec))
{
if(vec[i] < 0)
{
vec[i] = -vec[i]
}
}
}
# 代码2
abs_v = function(vec)
{
negs = vec < 0
vec[negs] = vec[negs] * -1
}
`` 重复生成某一值或某一向量
# 先指定需要重复生成的值或向量,再设定想要重复的次数;R会返回一个新的,更长的向量
rep(c(-1 , 1) , 5000000)
试比较如下两段代码
vec = c("DD" , "C" , "7" , "B" , "BB" , "BBB" , "0")
many = rep(vec , 1000000)
# 代码1
change_vec1 = function(vec)
{
vec[vec == "DD"] = "joker"
vec[vec == "C"] = "ace"
vec[vec == "7"] = "king"
vec[vec == "B"] = "queen"
vec[vec == "BB"] = "jack"
vec[vec == "BBB"] = "ten"
vec[vec == "0"] = "nine"
}
# 代码2,牺牲空间,换取时间
change_vec2 = function(vec)
{
tb = c("DD" = "joker" , "C" = "ace" , "7" = "king" , "B" = "queen" , "BB" = "jack" , "BBB" = "ten" , "0" = "nine")
unname(tb[vec])
}
- if 和 for 的协同工作常常可以被逻辑值取子集操作所替代
- 能放在 for 循环外的代码,就一定不要放在循环内
- 确保用来存储输出结果的对象具备足够的容量
附1. 导入与存储数据
获取工作目录
getwd()
改变工作目录
setwd()
查看当前工作目录的所有文件
list.files()
加载纯文本文件
read.table(
"poker.csv"【文件名】 ,
sep=","【行分隔符】 ,
header=TRUE【是否将第一行数据视为变量名】 ,
na.strings="."【缺失信息的符号】,
skip=3【忽略文件开头的一些行】,
nrow=5【读取某个数量的数据行之后就不再读取】,
stringsAsFactors=FALSE【不强制把字符串数据转换为因子】)
更改全局设置改变 R 将字符串转换为因子型的默认行为
options(stringsAsFactors=FALSE)
保存为纯文本文件
write.csv(r_object , file=filepath , row.names=FALSE)
RDS文件存储单个 R 对象,RData存储多个 R 对象
打开 RDS 文件
poker <- readRDS("poker.RDS")
打开 RData 文件
load("file.RData")
存储数据到 RDS
a <- 1
saveRDS(a , file="stuff.RDS")
存储数据到 RData
a <- 1
b <- 2
c <- 3
save(a , b , c file="stuff.RData")
将剪切板中的数据读入 R
read.table("clipboard")