一. 前言

查看已经命名了哪些 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 作用域规则

在搜索对象时

  1. 首先在当前活动环境中搜索对象
  2. 在命令行中工作时,活动环境就是全局环境;因此命令行的所有调用都是发生在全局环境
  3. 当在某个环境没有搜索到对象时,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 创建一个类

要想创建一个类,应该执行以下操作

  1. 给类起一个名称
  2. 给属于该类的每个对象赋 class 属性
  3. 为属于该类的对象编写常用泛型函数的类方法

要创建一个可靠,运行良好的类,需要做很多工作。通常来说,你需要为 R 中的每一个基本操作编写对应的类方法函数

在尝试编写类方法函数时,你会立即遇到两个挑战

  1. R 在将多个对象组合成一个向量时会丢弃对象的属性 *(现在好像不会丢弃了 ?)
  2. 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])       
}
  1. if 和 for 的协同工作常常可以被逻辑值取子集操作所替代
  2. 能放在 for 循环外的代码,就一定不要放在循环内
  3. 确保用来存储输出结果的对象具备足够的容量

附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")