学习小组Day6笔记--乳酸君

今天的内容主要是学习R包,包括包的安装、加载等;并且以dplyr包进行了举例,此包中主要包含了可对数据框进行操作的相关函数。

R包和dplyr包的基础应用

包的概念

包(package)是一系列R函数和数据集的集合;库(library)是电脑上的文件夹,包就存储于文件夹内的文件中。在我们按照标准的步骤下载安装了R之后,我们便已经默认安装了一些基础的包。

去哪儿找包

除了R语言的核心团队外,其他人也可以开发自己的包用于R语言功能的探索。大部分的包储存在名为CRAN(Comprehensive R Archive Network)的在线资源库中。我们可以应用setRepositories()命令查看究竟有哪些资源库或者更改默认访问的资源库。

> setRepositories()
--- 请选用一个貯藏處 ---


1: + CRAN
2:   BioC software
3:   BioC annotation
4:   BioC experiment
5:   CRAN (extras)
6:   Omegahat
7:   R-Forge
8:   rforge.net

Enter one or more numbers separated by spaces and then ENTER, or 0 to cancel

Bioconductor当中包含了很多分子生物学、基因组学等相关的包,与生物信息关系比较密切。我们可以用available.packages()函数查看某库当中包的信息。

除此之外,我们还可以在网上其他的存储库当中找到R的文件,如大名鼎鼎的Github以及bitbucket、Google Code等。

镜像设置

正如上面所说的那样,包被存储在在线的存储库里,如果我们想要使用的话,首先就要把它下载到我们自己的电脑上。但是我们用的是国内的网,储存库用的是国外的网,网络不好的时候下载就会出现问题或者耗时很长。我们可以利用镜像网站巧妙地解决这个问题,感谢生信星球将这个问题讲述的十分详细,下面是我自己实操的过程:

  • 方法1:在R studio的Tools→Global Options→Packages当中,在Primary CRAN repository中选择国内的镜像网站。但是应用起来很不稳定:
镜像设置
  • 方法2:利用options命令手动进行设置,代码如下。之后我们可以用options()$reposoptions()$BioC_mirror命令进行检查,发现确实更改成功了:
> options("repos" = c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/")) #对应清华源
> options(BioC_mirror="https://mirrors.ustc.edu.cn/bioc/") #对应中科大源
> options()$repos
                                        CRAN 
"https://mirrors.tuna.tsinghua.edu.cn/CRAN/" 
> options()$BioC_mirror
[1] "https://mirrors.ustc.edu.cn/bioc/"

 它的缺点也是并不稳定,当我退出了R studio后再打开时,又变回了国外的官方镜像:

> options()$repos
                       CRAN 
"https://cran.rstudio.com/" 
attr(,"RStudio")
[1] TRUE
> options()$BioC_mirror
NULL
  • 方法3:利用R的配置文件,我们首先设置file.edit('~/.Rprofile'),并在当中添加上面用到的两个option()代码:
    Rprofile当中的镜像设置

 保存后,当我们再度打开R studio时,发现镜像依然是国内网站。这是因为配置的原因,在打开R studio的时候,它已经首先运行了我们制定的option()命令:

> options()$repos
                                        CRAN 
"https://mirrors.tuna.tsinghua.edu.cn/CRAN/" 
> options()$BioC_mirror
[1] "https://mirrors.ustc.edu.cn/bioc/"

R studio中有两个最重要的配置文件,在刚开始运行Rstudio的时候,程序会查看许多配置内容:
.Renviron:设置R的环境变量;
.Rprofile:一个代码文件,如果启动时找到这个文件,那么就替我们先运行一遍

包的安装

  • 方法1:在Rstudio的Tools选项中点击Install Package
R studio的Tools界面
包的安装选项
  • 方法2:根据包的来源不同我们可以通过install.packages(“包”)命令或者
    BiocManager::install(“包”)命令进行包的安装。事实上,在想要安装一个包时,首先在网络上搜索一下它的名字,便会查找到许多相关的帮助。

install.packages常用的参数:
pkg:要安装的包的名字,如果是连接网络下载可直接写出报名,利用参数c(“包1”,"包2“)可以一次下载多个包。如果安装已经下载的包(tar.gz/tgz/zip文件),可以直接写上这些本地包的路径,并添加repo=NULL参数;
repos=:库的基础URL,如:CRAN 镜像的URL;如为NULL,表示包已经被下载;
lib=:储存到哪个库中;如果缺少,默认为. libpaths()的第一个元素;
type=:包的类型,除了windows和一些Mac OS系统外,为“source”,其余的类型包括二进制类型等

  • 方法3:从Github上安装包,具体方法如下:
install.packages("devtools") #首先安装devtools包
library(usethis) #加载devtools之前提示我首先加载usethis包
library(devtools) #加载devtools包
install_github("包") #利用install_github包下载所需的包

包的加载

通常我们可以使用library()来进行包的加载,包的名称在传递给库library时并不需要被引号括起来。当然我们能加载出来一个包的前提是我们已经安装好了这个包,否则将会抛出一个错误。

另一种加载包的方式为require(),但是它不会抛出错误,而是通过返回TRUE和FALSE来判断包是否被成功加载。

在下面的例子中,我们安装了dplyr包并进行了加载:

options("repos" = c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/")) 
options(BioC_mirror="https://mirrors.ustc.edu.cn/bioc/") 
install.packages("dplyr")
library(dplyr)

查看已安装的包

installed.packages()将为我们返回已安装包的信息,包括它的名称、版本、位置、依赖的包等。为了更清晰直观地进行查看,我们可以结合View()命令远离控制台进行查看:

View(installed.packages())
查看已经安装的包

此外,下面的代码将提供给我们一些关于已安装的包的位置的信息:

#查看R安装时自带的包的位置
> R.home("library") 
[1] "/Library/Frameworks/R.framework/Resources/library"
> .Library 
[1] "/Library/Frameworks/R.framework/Resources/library"
#查看系统用户库的位置
> path.expand("~") 
[1] "/Users/maxxie"
> Sys.getenv("HOME") 
[1] "/Users/maxxie"
#返回的第一个结果为包被默认安装的位置
> .libPaths() 
[1] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library"

查看已加载的包

我们可以使用search()查看已经加载的包。其中第一位的永远是全局环境,因而全局环境也始终在搜索列表的最前面;之后的为我们最近加载过的包;排在最末的两个一个为特殊环境Autoloads,另一个为基础包base:

> search()
 [1] ".GlobalEnv"        "package:devtools"  "package:usethis"   "package:dplyr"    
 [5] "tools:rstudio"     "package:stats"     "package:graphics"  "package:grDevices"
 [9] "package:utils"     "package:datasets"  "package:methods"   "Autoloads"        
[13] "package:base" 

包的维护

包括包的升级以及包的删除,具体命令如下:

update.packages() #将安装的包全部升级到最新版本
update.packages(ask = FALSE) #加上ask=FALSE参数,在更新时不会进行提示
remove.packages("包") #删除某个包

dplyr包基础函数

以dplyr包中自带的数据集iris进行演示。该数据集向我们展示了来自三种鸢尾属植物、共50种花的花萼长度与重量,花瓣长度与重量。我们利用[]按照数据库索引的方式从中随机挑选几行赋值给新的数据框test:

test <- iris[c(1:2,51:52,101:102),] #以其中的iris数据集进行演示

下面的例子中涉及到与dplyr包中函数功能类似的base包中的函数,也选择性地进行了探索与展示:

  • 添加列
    向test数据框中添加一列new,其值为 Sepal.Length * Sepal.Width
    ① 我们首先可以用标准赋值符进行操作;
> test$new <- test$Sepal.Length * test$Sepal.Width
> test
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species   new
1            5.1         3.5          1.4         0.2     setosa 17.85
2            4.9         3.0          1.4         0.2     setosa 14.70
51           7.0         3.2          4.7         1.4 versicolor 22.40
52           6.4         3.2          4.5         1.5 versicolor 20.48
101          6.3         3.3          6.0         2.5  virginica 20.79
102          5.8         2.7          5.1         1.9  virginica 15.66

 如果重复打数据框的名字太麻烦,则可以使用with()或者within(),分别对单列或者多列进行更改。当然,Day4中用过的attach()也是有效的。

 ② 我们还可以使用mutate(数据框,新列名=值)函数,进行列的添加:

> mutate(test, new = Sepal.Length * Sepal.Width)
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species   new
1          5.1         3.5          1.4         0.2     setosa 17.85
2          4.9         3.0          1.4         0.2     setosa 14.70
3          7.0         3.2          4.7         1.4 versicolor 22.40
4          6.4         3.2          4.5         1.5 versicolor 20.48
5          6.3         3.3          6.0         2.5  virginica 20.79
6          5.8         2.7          5.1         1.9  virginica 15.66
  • 列的筛选
    筛选出某些想要的列进行展示
    除了利用[]进行索引外,还可以使用select(数据框,列名或列号)函数进行筛选:
#按列号进行筛选
> select(test,1)
    Sepal.Length
1            5.1
2            4.9
51           7.0
52           6.4
101          6.3
102          5.8
> select(test,c(1,5))
    Sepal.Length    Species
1            5.1     setosa
2            4.9     setosa
51           7.0 versicolor
52           6.4 versicolor
101          6.3  virginica
102          5.8  virginica

#按列名进行筛选
> select(test,Sepal.Length)
    Sepal.Length
1            5.1
2            4.9
51           7.0
52           6.4
101          6.3
102          5.8
  • 行的筛选
    筛选出某些想要的行进行展示
    我们使用filter(数据框,逻辑式)函数对行进行筛选,不同于select()直接索引列名或列号,filter()接受一个逻辑表达式并返回结果为TRUE的行:
#返回物种名为"setosa"的行
> filter(test, Species == "setosa") 
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

#返回物种名为"setosa"且花萼长度大于5的行
> filter(test, Species == "setosa"&Sepal.Length > 5 )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa

#提取物种在向量c("setosa","versicolor")中的那几行,即物种为"setosa"或"versicolor"的行;
#A%in%B的含义为判断A的值是否包含在B当中,返回布尔值
> filter(test, Species %in% c("setosa","versicolor")) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          5.1         3.5          1.4         0.2     setosa
2          4.9         3.0          1.4         0.2     setosa
3          7.0         3.2          4.7         1.4 versicolor
4          6.4         3.2          4.5         1.5 versicolor
  • 排序
    对某一列按照某种顺序进行排序
    arrange(数据框,按照什么进行排序)可仅用一行便对数据框进行排序:
arrange(test, Sepal.Length) #默认从小到大排序
##   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1          4.9         3.0          1.4         0.2     setosa
## 2          5.1         3.5          1.4         0.2     setosa
## 3          5.8         2.7          5.1         1.9  virginica
## 4          6.3         3.3          6.0         2.5  virginica
## 5          6.4         3.2          4.5         1.5 versicolor
## 6          7.0         3.2          4.7         1.4 versicolor

arrange(test, desc(Sepal.Length)) #用desc从大到小
##   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1          7.0         3.2          4.7         1.4 versicolor
## 2          6.4         3.2          4.5         1.5 versicolor
## 3          6.3         3.3          6.0         2.5  virginica
## 4          5.8         2.7          5.1         1.9  virginica
## 5          5.1         3.5          1.4         0.2     setosa
## 6          4.9         3.0          1.4         0.2     setosa

 ②order(x)将返回x中的数值按照由小到大排序后的排名情况,配合索引可以在数据框中进行某种顺序的排序:

> sepal.Length.order<-order(test$Sepal.Length)
> sepal.Length.order #得到的结果为test中各行的Sepal.Length由小到大的排名情况
[1] 2 1 6 5 4 3
> test[sepal.Length.order,]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
2            4.9         3.0          1.4         0.2     setosa
1            5.1         3.5          1.4         0.2     setosa
102          5.8         2.7          5.1         1.9  virginica
101          6.3         3.3          6.0         2.5  virginica
52           6.4         3.2          4.5         1.5 versicolor
51           7.0         3.2          4.7         1.4 versicolor

 ③sort()函数可以按照一定顺序对数值或者字符进行排序,但是数据框无法直接应用

x<-c(2,32,4,16,8)
sort(x)
sort(x,decreasing = TRUE)
sort(c("I","A","B","E","R"))

 ④rank()函数也可以像sort()一样给出排名,但是在有排名相同的情况时,根据参数的不同会给出不一样的处理方式:

> (x<-sample(3,7,replace = TRUE))
[1] 1 2 2 3 3 3 2
> rank(x)
[1] 1 3 3 6 6 6 3
> rank(x,ties.method = "first")
[1] 1 2 3 5 6 7 4
  • 汇总
    summarize()函数可以对常用的统计学指标进行汇总,常见的指标如下:

常用的统计学指标

  • Center: mean(),median()
  • Spread: sd(),IQR(),mad()
  • Range: min(),max(),quantile()
  • Position: first(), last(), nth()
  • Count: n(),n_distinct()
  • Logical: any(),all()

 我们还可以结合分组函数group_by()先分组再汇总:

#计算Sepal.Length的平均值和标准差
summarise(test, mean(Sepal.Length), sd(Sepal.Length)) 
##   mean(Sepal.Length) sd(Sepal.Length)
## 1           5.916667        0.8084965

#先按照Species分组,计算每组Sepal.Length的平均值和标准差
group_by(test, Species)
## # A tibble: 6 x 5
## # Groups:   Species [3]
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species   
## *        <dbl>       <dbl>        <dbl>       <dbl> <fct>     
## 1          5.1         3.5          1.4         0.2 setosa    
## 2          4.9         3            1.4         0.2 setosa    
## 3          7           3.2          4.7         1.4 versicolor
## 4          6.4         3.2          4.5         1.5 versicolor
## 5          6.3         3.3          6           2.5 virginica 
## 6          5.8         2.7          5.1         1.9 virginica

#汇总分组后的结果
summarise(group_by(test, Species),mean(Sepal.Length), sd(Sepal.Length))
## # A tibble: 3 x 3
##   Species    `mean(Sepal.Length)` `sd(Sepal.Length)`
##   
## 1 setosa                     5                 0.141
## 2 versicolor                 6.7               0.424
## 3 virginica                  6.05              0.354
  • 统计某列的unique值
    在某列中有一个数值x出现了n次,如何统计x出现了几次
    count(数据框,列名)可以迅速实现这一目标,下面就对test数据集中物种每个物种出现了几次进行了统计:
count(test,Species)
## # A tibble: 3 x 2
##   Species        n
##   
## 1 setosa         2
## 2 versicolor     2
## 3 virginica      2
  • 管道符号
    就像工厂当中的流水线一样,一个产品经过了某个工人A的操作后直接传到下一个工人B的手里,B直接拿到了A加工后的半成品。
    R语言当中的%>% (快捷键为:cmd/ctr + shift + M)则起到了管道一样的功能,可以将前一步完成得到的那个结果直接传给右边/下一个函数,而不用再次将前一步骤得到的结果再次进行输入。
    运行后,结果将一气呵成地进行展现,你将直接看到最终的结果:
> test %>%   #将test数据框传递给group_by()函数
+   group_by(Species) %>%  #按物种group_by()的结果传递给summarise()
+   summarise(mean(Sepal.Length), sd(Sepal.Length))
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 3 x 3
  Species    `mean(Sepal.Length)` `sd(Sepal.Length)`
  <fct>                     <dbl>              <dbl>
1 setosa                     5                 0.141
2 versicolor                 6.7               0.424
3 virginica                  6.05              0.354

数据框的连接

数据框A和B两者有着相同的某些行或某些列,比如一个A表中包括学生的学号、姓名和数学成绩,B表中包含学号、姓名和语文成绩,如何根据学号将两表合并为学号、姓名、数学成绩、语文成绩的综合展现也是现实当中常常会遇到的问题:

下面的操作以两个数据库test1和test2进行展示。两者相同的列为x,但是test1和test2里x列中的值有相同,也有不同:

> options(stringsAsFactors = F)
> test1 <- data.frame(x = c('b','e','f','x'), 
+                     z = c("A","B","C",'D'),
+                     stringsAsFactors = F)
> test1
  x z
1 b A
2 e B
3 f C
4 x D
> test2 <- data.frame(x = c('a','b','c','d','e','f'), 
+                     y = c(1,2,3,4,5,6), stringsAsFactors = F)
> test2                  
  x y
1 a 1
2 b 2
3 c 3
4 d 4
5 e 5
6 f 6
  • 内连接
    取交集,函数为inner_join()
> inner_join(test1, test2, by = 'x')
  x z y
1 b A 2
2 e B 5
3 f C 6

其中参数by表示按照什么进行连接:
① 如果为空,则会寻找test1和test2中所有相同的列名取交集;
② 如果输入某一列名,如上面by="x",则取x列中值相同的取交集;
③ 如果输入向量,如c("x","z"),则分别取test1的x和test2的x ,text1的z和test2的z的交集;
④ 如果输入一个含等号的向量,如c("a"="b","c"="d"),则取test1中a和test2中的b,test1中的c和test2中的d分别配对后取交集

  • 左、右连接
    left_join(x,y)会保留x中所有行,而right_join(x,y)保留y中所有行:
#左连接,test2中相应位置没有的值被NA替代
> left_join(test1, test2, by = 'x')
  x z  y
1 b A  2
2 e B  5
3 f C  6
4 x D NA

#右连接,test1中相应位置没有的值被NA替代
> right_join(test1, test2, by = 'x')
  x    z y
1 b    A 2
2 e    B 5
3 f    C 6
4 a <NA> 1
5 c <NA> 3
6 d <NA> 4
  • 全连接
    full_join(test1,test2,by='列名')可以将两列表直接全部按照给定列名匹配后连接,相应空缺的位置被NA取代:
> full_join( test1, test2, by = 'x')
  x    z  y
1 b    A  2
2 e    B  5
3 f    C  6
4 x    D NA
5 a <NA>  1
6 c <NA>  3
7 d <NA>  4
  • 半连接
    如果我们对两个表格进行匹配后只想保留其中一个表格的信息,可以使用半连接semi_join()。例如下面的例子只展示了能与test2中x列匹配的test1的值:
> semi_join(test1,test2,by="x")
  x z
1 b A
2 e B
3 f C
  • 反连接
    与半连接类似,反连接anti_join只保留不能与test2匹配的那部分test1当中的数值。将两个例子放在一起展示更为直观:
> semi_join(test1,test2,by="x")
  x z
1 b A
2 e B
3 f C
> anti_join(x=test1,y=test2,by="x")
  x z
1 x D
  • 简单连接
    base包中有两个连接函数rbind()cbind(),如果两个数据框的大小一致,可以分别将它们“叠起来”和“左右连接拼起来”。其中rbind()会智能地对列重新排序并检查,但是cbind()不会对列名进行重复性检查,因此要小心使用。
    dplyr包中的bind_rows()bind_cols()分别与base包中的rbind()cbind()功能一致。其中,bind_rows()函数需要两个表格列数相同,而bind_cols()函数则需要两个数据框有相同的行数:
> test1 <- data.frame(x = c(1,2,3,4), y = c(10,20,30,40))
> test1
  x  y
1 1 10
2 2 20
3 3 30
4 4 40
> test2 <- data.frame(x = c(5,6), y = c(50,60))
> test2
  x  y
1 5 50
2 6 60
> test3 <- data.frame(z = c(100,200,300,400))
> test3
    z
1 100
2 200
3 300
4 400
> bind_rows(test1, test2)
  x  y
1 1 10
2 2 20
3 3 30
4 4 40
5 5 50
6 6 60
> bind_cols(test1, test3)
  x  y   z
1 1 10 100
2 2 20 200
3 3 30 300
4 4 40 400

 此外,如果两个数据框具有相同的列,我们还可以用merge()函数进行连接,我们可以指定by=’x‘参数指定需要共享的那一列。如果添加all=TRUE参数,则展示全部的数据框内容,无法匹配的内容将被用NA替代:

> test1 <- data.frame(x = c(1,2,3,4), y = c(10,20,30,40))
> test1
  x  y
1 1 10
2 2 20
3 3 30
4 4 40
> test2 <- data.frame(x = c(1,2,5,6), y = c(30,40,50,60))
> test2
  x  y
1 1 30
2 2 40
3 5 50
4 6 60
> merge(test1,test2,by='x')
  x y.x y.y
1 1  10  30
2 2  20  40
> merge(test1,test2,by='x',all = TRUE)
  x y.x y.y
1 1  10  30
2 2  20  40
3 3  30  NA
4 4  40  NA
5 5  NA  50
6 6  NA  60
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342