1. 程式人生 > >R語言中tidyverse基礎知識彙總

R語言中tidyverse基礎知識彙總

tidyverse

group_by 分組統計

gather()和spread()

簡單地說,gather()是列轉行,而spread()是行轉列。
請看下面的示例:

> df
   id class grade
1   1     a    81
2   2     b    82
3   3     a    83
4   4     b    84
5   5     a    85
6   6     b    86
7   7     a    87
8   8     b    88
9   9     a    89
10 10     b    90

*可以使用spread()將class中的某個欄位的取值拆分成多個列。

> df_2 = df %>% spread(key = 'class', value = 'grade')
> df_2
  id  a  b
1  1 81 82
2  2 83 84
3  3 85 86
4  4 87 88
5  5 89 90

*也可以使用gather()將df_2中的a列和b列轉換成某欄位的值,就是把多列欄位聚合在一起。

> df_2 %>% gather("a", "b", key = 'class', value = 'grade')
   id class grade
1   1     a    81
2   2     a    83
3   3     a    85
4   4     a    87
5   5     a    89
6   1     b    82
7   2     b    84
8   3     b    86
9   4     b    88
10  5     b    90

unite()和separate()

這個一般用於字串,也可以用於數字,就是把一列分成兩列,或者把兩列拼成一列。
unite()預設使用"_"來拼接,也可以用sep來指定符號。

> df_3 = df_2 %>% unite('ab', 'a', 'b', sep='*')
  id    ab
1  1 81*82
2  2 83*84
3  3 85*86
4  4 87*88
5  5 89*90

separate()中一般用非數字字元的符號作為分割符,也可以用sep指定分割符,支援正則。

> df_4 = df_3 %>% separate("ab", into=c("a", "b"))
  id  a  b
1  1 81 82
2  2 83 84
3  3 85 86
4  4 87 88
5  5 89 90

注意,這裡的數字都會被處理成字串:

> str(df_4)
'data.frame':   5 obs. of  3 variables:
 $ id: num  1 2 3 4 5
 $ a : chr  "81" "83" "85" "87" ...
 $ b : chr  "82" "84" "86" "88" ...
> str(df_2)
'data.frame':   5 obs. of  3 variables:
 $ id: num  1 2 3 4 5
 $ a : int  81 83 85 87 89
 $ b : int  82 84 86 88 90

tibble

可以使用as_tibble把dataframe轉換成tibble格式,也可以直接構造一個tibble。
如果你已經熟悉了data.frame(),請注意:tibble永遠不會改變輸入的型別(例如,它永遠不會將字串轉換為因子!)

> a = tibble(a = c("yang", "y"), b = c(1,2))
> a
# A tibble: 2 x 2
  a         b
  <chr> <dbl>
1 yang      1
2 y         2
#rep()中的each引數是讓每個數重複多少次,times引數是整體重複多少次。
> tibble(a = rep(1:3, each = 3))
# A tibble: 9 x 1
      a
  <int>
1     1
2     1
3     1
4     2
5     2
6     2
7     3
8     3
9     3

在引用欄位時,可以使用a$a 或者 a[['a']]。

> a[['a']]
[1] "yang" "y"   

在管道符號中,要注意加個點,如'.$x' 或者'.[['a']]'。

> a %>% .[['a']]
[1] "yang" "y"

注意,這種表示方法在管道符中比較好用,因為可以用變數名代替欄位名,這點很重要,以前不知道。

> x = 'a'
> a %>% .[[x]] #否則會把變數名直接當欄位名,即認為欄位為"x"
[1] "yang" "y" 

連線

 過濾連線 semi_join, anti_join

用於對資料進行篩選

> m
  id value
1  1     2
2  2     3
3  3     4
> n
  id value
1  0     2
2  2     3
3  3     5
#可以在向量中指定兩邊連線的欄位,一個等號。
#對m表進行篩選,只保留匹配到的記錄。
> m %>% semi_join(n, c("id" = "id")) 
  id value
1  2     3
2  3     4
#對m表進行篩選,只保留匹配不到的記錄。
> m %>% anti_join(n, "id")
  id value
1  1     2

常用連線

inner_join(x, y)    merge(x, y)
left_join(x, y)     merge(x, y, all.x = TRUE)
right_join(x, y)    merge(x, y, all.y = TRUE),
full_join(x, y)     merge(x, y, all.x = TRUE, all.y = TRUE)

dplyr中的連線速度比merge()快很多。

集合操作

針對表中每行記錄進行操作。
intersect(x, y):僅返回在兩表中共同的行。
union(x, y):返回兩表記錄的並集。
setdiff(x, y):返回存在於x表中,但不在y表中的記錄。(semi_join只考慮某個欄位)

> union(m,n)
  id value
1  1     2
2  3     5
3  3     4
4  0     2
5  2     3
> intersect(m, n)
  id value
1  2     3
> setdiff(m,n)
  id value
1  1     2
2  3     4

字串

library(tidyverse)
library(stringr)
R中的基礎函式也可以處理字串,但不好記憶,我每次都要上網查詢,所以用stringr包,這個包要單獨載入。
這個包中的函式一般以str_開頭。

顯示字串原來內容,考慮轉義。

> writeLines("\"")
"
> print("\"")
[1] "\""

長度

str_length(c("a", "R for data science", NA))
#> [1]  1 18 NA

拼接

可以對單個字串拼接,也可以對含有多個字串的向量拼接。

str_c("x", "y", sep = ", ")
#> [1] "x, y"

> name = c("yang", "jian")
> str_c(name, c("@", "$"))
[1] "[email protected]" "jian$"

> str_c("_", name, "_")
[1] "_yang_" "_jian_"

#str_c()是向量化的,如果兩向量的元素個數不相等,會迴圈執行,並丟擲警告。
> str_c(name, c("@", "$", "#"))
[1] "[email protected]" "jian$" "yang#"
Warning message:
In stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE) :
  longer object length is not a multiple of shorter object length

# NA值處理
> str_c("a", NA)
[1] NA

> str_replace_na(NA)
[1] "NA" #字串NA
> class(str_replace_na(NA))
[1] "character"

# 加入條件判斷。空串不顯示。
> a = 20
> str_c("a", "b", '', if(a>=18) "yj")
[1] "abyj"

#要將字串向量摺疊為單個字串,請使用collapse:
str_c(c("x", "y", "z"), collapse = ", ")
#> [1] "x, y, z"

擷取字串

x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
#> [1] "App" "Ban" "Pea"

# 修改和替換
str_sub(x, 1, 1) <- str_to_lower(str_sub(x, 1, 1))
x
#> [1] "apple"  "banana" "pear"

大小寫及排序。(考慮語言環境)

str_to_lower()
str_to_upper()
str_to_title() #首字母大寫
> str_sort(c("apple", "banana", "pear" ))
[1] "apple"  "banana" "pear"  
> str_order(c("apple", "banana", "pear" ))
[1] 1 2 3

正則表示式

str_view()和str_detect()
str_view()的返回結果不知道是啥?
str_detect()是檢測與正則是否匹配,返回邏輯結果。
str_extract() 提取匹配。

> s = c("abc", "dc", "aa")
> str_detect(s, 'c$')
[1]  TRUE  TRUE FALSE
> s[str_detect(s, 'c$')]
[1] "abc" "dc"

#或者直接用下面這種方法
> str_subset(s,"c$")
[1] "abc" "dc" 

#還可以在資料框中進行篩選,直接作為管道符中的函式,類似於sum(),n()等。
> df_3
  id    ab
1  1 81*82
2  2 83*84
3  3 85*86
4  4 87*88
5  5 89*90
> df_3 %>% filter(str_detect(ab, "2$"))
  id    ab
1  1 81*82

#統計匹配的數量
> x <- c("apple", "banana", "pear")
> str_count(x, "an")
[1] 0 2 0
#統計ab列中8的次數。
> df_3 %>% mutate( count = str_count(ab,"8"))
  id    ab count
1  1 81*82     2
2  2 83*84     2
3  3 85*86     2
4  4 87*88     3
5  5 89*90     1

str_extract()僅提取第一個匹配項。我們可以通過首先選擇具有多於1個匹配的所有句子來最容易地看到。
str_extract_all() 以列表形式返回所有匹配到的內容。
str_extract_all(, simplify = T) 以矩陣形式返回,用空串補充。

> x
[1] "apple"  "banana" "pear"  

> str_extract(x, "[a-e]")
[1] "a" "b" "e"

> str_extract_all(x, "[a-e]")
[[1]]
[1] "a" "e"

[[2]]
[1] "b" "a" "a" "a"

[[3]]
[1] "e" "a"

> str_extract_all(x, "[a-e]", simplify = T)
     [,1] [,2] [,3] [,4]
[1,] "a"  "e"  ""   ""  
[2,] "b"  "a"  "a"  "a" 
[3,] "e"  "a"  ""   "" 

str_match()給出每個單獨的元件。它返回一個矩陣,而不是字元向量,其中一列用於完整匹配,後面是每個分組的匹配,也就正則表示式中小括號中匹配的內容。

> sen = c("the apple", "a pen", "the banana")
> noun <- "(a|the) ([^ ]+)" #第二個括號內容表示空格以外的任何字元,重複一次或者多次。
> str_match(sen, noun)
     [,1]         [,2]  [,3]    
[1,] "the apple"  "the" "apple" 
[2,] "a pen"      "a"   "pen"   
[3,] "the banana" "the" "banana"

str_replace()並str_replace_all()允許您用新字串替換匹配項。最簡單的用法是用固定的字串替換模式。

x <- c("apple", "pear", "banana")
str_replace(x, "[aeiou]", "-")
#> [1] "-pple"  "p-ar"   "b-nana"
str_replace_all(x, "[aeiou]", "-")
#> [1] "-ppl-"  "p--r"   "b-n-n-"

隨著str_replace_all()您可以通過提供一個名為向量執行多個替換:

x <- c("1 house", "2 cars", "3 people")
str_replace_all(x, c("1" = "one", "2" = "two", "3" = "three"))
#> [1] "one house"    "two cars"     "three people"

可以使用反向引用來插入匹配的元件,而不是使用固定字串替換。

> head(sentences )
[1] "The birch canoe slid on the smooth planks." 
[2] "Glue the sheet to the dark blue background."
[3] "It's easy to tell the depth of a well."     
[4] "These days a chicken leg is a rare dish."   
[5] "Rice is often served in round bowls."       
[6] "The juice of lemons makes fine punch."  
# 匹配到每句的前三個單詞,用\\1方式引用和處理。    
> sentences %>% 
+     str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") %>% 
+     head(5)
[1] "The canoe birch slid on the smooth planks." 
[2] "Glue sheet the to the dark blue background."
[3] "It's to easy tell the depth of a well."     
[4] "These a days chicken leg is a rare dish."   
[5] "Rice often is served in round bowls." 

> sentences %>% 
+     str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1_\\3_\\2") %>% 
+     head(5)
[1] "The_canoe_birch slid on the smooth planks." 
[2] "Glue_sheet_the to the dark blue background."
[3] "It's_to_easy tell the depth of a well."     
[4] "These_a_days chicken leg is a rare dish."   
[5] "Rice_often_is served in round bowls."  

str_split()最多將一個字串分解成多個。例如,我們可以將句子分成單詞。
返回一個列表, 您可以使用simplify = TRUE返回矩陣。

> "the apple is red" %>% str_split(" ")
[[1]]
[1] "the"   "apple" "is"    "red" 

str_locate()和str_locate_all()給你查詢匹配項的開始和結束位置。當其他任何功能都沒有完全符合您的要求時,這些功能特別有用。您可以使用它str_locate()來查詢匹配的模式,str_sub()以提取和/或修改它們。

> str_locate("app_cpp", "pp")
     start end
[1,]     2   3
> str_locate_all("app_cpp", "pp")
[[1]]
     start end
[1,]     2   3
[2,]     6   7

您可以使用其他引數regex()來控制匹配的詳細資訊。
ignore_case = TRUE,允許字元匹配大寫或小寫形式。這始終使用當前區域設定。
multiline = TRUE,允許^和$匹配每一行的開頭和結尾,而不是整個字串的開頭和結尾。

x <- "Line 1\nLine 2\nLine 3"
str_extract_all(x, "^Line")[[1]]
#> [1] "Line"
str_extract_all(x, regex("^Line", multiline = TRUE))[[1]]
#> [1] "Line" "Line" "Line"

comments = TRUE,允許您使用註釋和空格來使複雜的正則表示式更容易理解。
dotall = TRUE,允許.匹配所有內容,包括\n。

其他

apropos()搜尋全域性環境中可用的所有物件。如果您不太記得函式的名稱,這將非常有用。
dir()列出目錄中的所有檔案。該pattern引數採用正則表示式,僅返回與模式匹配的檔名。

> dir(pattern='.*xlsx')
[1] "yj.xlsx.xlsx"

因子factors

library(tidyverse)
library(forcats)

#如果對月份排序,預設是按字母順序排列的。如果按月份排序,應該使用因子。
x1 <- c("Dec", "Apr", "Jan", "Mar")
sort(x1)
#[1] "Apr" "Dec" "Jan" "Mar"
month_levels <- c(
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)
y1 <- factor(x1, levels = month_levels)
sort(y1)
#[1] Jan Mar Apr Dec
# Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

日期和時間

本章將重點介紹lubridate包,它使得更容易處理R. Rubridate中的日期和時間不是核心整數的一部分,因為您只需要在處理日期/時間時使用它。我們還需要nycflights13來練習資料。

library(tidyverse)
library(lubridate)
library(nycflights13)

字串轉換成日期

ymd("2017-01-31")
#> [1] "2017-01-31"
mdy("January 31st, 2017")
#> [1] "2017-01-31"
dmy("31-Jan-2017")
#> [1] "2017-01-31"
ymd(20170131)
#> [1] "2017-01-31"
ymd_hms("2017-01-31 20:11:59")
#> [1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:01")
#> [1] "2017-01-31 08:01:00 UTC"

如果用as.Date(),

> as.Date("2018-10-30")
[1] "2018-10-30"
> as.Date("20181030")
Error in charToDate(x) : 字串的格式不夠標準明確

日期和時間轉換。

as_datetime(today())
#> [1] "2018-10-25 UTC"
as_date(now())
#> [1] "2018-10-25"

讀取日期和時間元件

#"2018-11-08",週四
> t = today()
[1] "2018-11-08"
> year(t)
[1] 2018
> month(t)
[1] 11
> week(t)
[1] 45
> day(t)
[1] 8
> yday(t)
[1] 312
> mday(t)
[1] 8
> wday(t)
[1] 5 
> wday(ymd('20181111'))
[1] 1
> wday(ymd('20181111'), label = T)
[1] 週日
Levels: 週日 < 週一 < 週二 < 週三 < 週四 < 週五 < 週六
> wday(ymd('20181111'), label = T, abbr = F)
[1] 星期日
Levels: 星期日 < 星期一 < 星期二 < 星期三 < 星期四 < 星期五 < 星期六
> second(now())
[1] 6.943398
> hour(now())
[1] 16
> minute(now())
[1] 27

日期計算
時間跨度(duration)
在R中,當你減去兩個日期時,你得到一個difftime物件。

> h_age <- today() - ymd(19791014)
> h_age
Time difference of 14270 days
# 以秒計算時間跨度。
> as.duration(h_age)
[1] "1232928000s (~39.07 years)"
> m = ymd('20181101')
> day(m) = day(m) -5
> m
[1] "2018-10-27"
> diff = ymd('20181101') - ymd('20181031')
> diff
Time difference of 1 days

可以用函式直接構造時間跨度函式,用於計算。

dseconds(15)
#> [1] "15s"
dminutes(10)
#> [1] "600s (~10 minutes)"
dhours(c(12, 24))
#> [1] "43200s (~12 hours)" "86400s (~1 days)"
ddays(0:5)
#> [1] "0s"                "86400s (~1 days)"  "172800s (~2 days)"
#> [4] "259200s (~3 days)" "345600s (~4 days)" "432000s (~5 days)"
dweeks(3)
#> [1] "1814400s (~3 weeks)"
dyears(1)
#> [1] "31536000s (~52.14 weeks)"

如果要計算明天的今天,

> today() + dyears(1)
[1] "2019-11-08"

週期(periods)
由於上面的時間跨度是精確到秒的,在計算時過於精確,在考慮時區時會出問題。為了解決這個問題,lubridate提供了週期,以更直觀的方式工作。

seconds(15)
#> [1] "15S"
minutes(10)
#> [1] "10M 0S"
hours(c(12, 24))
#> [1] "12H 0M 0S" "24H 0M 0S"
days(7)
#> [1] "7d 0H 0M 0S"
months(1:6)
#> [1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S"
#> [5] "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"
weeks(3)
#> [1] "21d 0H 0M 0S"
years(1)
#> [1] "1y 0m 0d 0H 0M 0S"

間隔(interval)

> ymd("2016-01-01") %--% ymd('2017-01-01') / days(1)
[1] 366
> (ymd('2017-01-01')-ymd("2016-01-01")) / ddays(1)
[1] 366

時區
檢視當前時區

> Sys.timezone()
[1] "Asia/Taipei"

修改時區,分兩種情況。一是保持時間不變,轉換成其他時區的時間;二是直接修改時區,會使時間改變。

t1 = now()
t2 = with_tz(now(), tzone = "Australia/Lord_Howe")
t3 = force_tz(now(), tzone = "Australia/Lord_Howe")
> t1
[1] "2018-11-08 17:53:38 CST"
> t2
[1] "2018-11-08 20:53:38 +11"
> t3
[1] "2018-11-08 17:53:38 +11"
#請忽略程式執行的時間差,即:t2 = t1
> t2-t1 
Time difference of 0.002995968 secs
> t3-t1
Time difference of -2.999998 hours

邏輯條件
在if()條件的計算結果必須要麼TRUE或FALSE。如果它是一個向量,你會收到一條警告資訊; 如果它是NA 或NULL,你會得到一個錯誤。但ifelse()與if()不一樣,兩者區別很大。
在if的條件中,儘量使用||和&&,這是惰性求值。或者用any()(存在一個TRUE就返回TRUE)、all()(所有為TRUE才返回TRUE)來把多個邏輯結果轉換成一個邏輯結果。

> T | c(T,T,F)
[1] TRUE TRUE TRUE
> T || c(T,T,F)
[1] TRUE

向量、列表、迴圈
列表結構中,用一箇中括號得到的還是一個列表,可以用雙層中括號或者$符號獲取元素。

> yj = list(a = 3, b = c(1,2))
> str(yj)
List of 2
 $ a: num 3
 $ b: num [1:2] 1 2
> str(yj[[1]])
 num 3
> str(yj$a)
 num 3
> str(yj[['a']])
 num 3

while和for迴圈
while更有用一些,可以控制迴圈的次數。

for (i in seq_along(x)) {
  # body
}

# Equivalent to
i <- 1
while (i <= length(x)) {
  # body
  i <- i + 1 
}

map函式,類似於apply函式,但比apply()函式快。

map()     返回列表。
map_lgl() 返回一個邏輯向量。
map_int() 返回一個整數向量。
map_dbl() 返回雙精度向量。
map_chr() 返回一個字元向量。
map_df()  返回資料框。

比較執行速度,如果迴圈次數少的話,絕對時間差不大;如果迴圈總人數多,差距巨大。

f = function(x){x + 1}

t1 = now()
map_dbl(c(1:10000000),f)
now() -t1
#Time difference of 10.76235 secs

t1 = now()
sapply(c(1:10000000),f)
now() -t1
#Time difference of 20.59263 secs

t1 = now()
res = c()
for(x in c(1:10000000)){res = c(res, f(x))}
now() -t1
#跑了好幾十分鐘也跑不出來,放棄了。

map2()和pmap()
如果函式有多個引數:

> f2 = function(x,y){x+y}
> a = c(1,2,3)
> b = c(2,3,4)
> map2(a, b, f2)
[[1]]
[1] 3

[[2]]
[1] 5

[[3]]
[1] 7
> pmap(list(a, b), f2) #需要放在列表中。
[[1]]
[1] 3

[[2]]
[1] 5

[[3]]
[1] 7

invoke_map()函式,呼叫不同的函式。

f <- c("runif", "rnorm", "rpois")
param <- list(
  list(min = -1, max = 1), 
  list(sd = 5), 
  list(lambda = 10)
)
invoke_map(f, param, n = 5) %>% str()
#> List of 3
#>  $ : num [1:5] 0.762 0.36 -0.714 0.531 0.254
#>  $ : num [1:5] 3.07 -3.09 1.1 5.64 9.07
#>  $ : int [1:5] 9 14 8 9 7

reduce()accumulate()
reduce函式採用“二進位制”函式(即具有兩個主輸入的函式),並將其重複應用於列表,直到只剩下一個元素。
下面示例中full_join()就有兩個主輸入的函式。

dfs <- list(
  age = tibble(name = "John", age = 30),
  sex = tibble(name = c("John", "Mary"), sex = c("M", "F")),
  trt = tibble(name = "Mary", treatment = "A")
)

dfs %>% reduce(full_join)
#> Joining, by = "name"
#> Joining, by = "name"
#> # A tibble: 2 x 4
#>   name    age sex   treatment
#>   <chr> <dbl> <chr> <chr>    
#> 1 John     30 M     <NA>     
#> 2 Mary     NA F     A

accumulate()與reduce()是類似的,但它保留了所有的中期結果。

> f2
function(x,y){x+y}
<bytecode: 0x0000000072c61b78>
> a = c(1,2,3,4)
> reduce(a, f2)
[1] 10
> res = accumulate(a, f2)
> res
[1]  1  3  6 10
> str(res)
 num [1:4] 1 3 6 10

謂詞函式
keep()與discard(),保留或者丟棄資料;

iris %>% 
  keep(is.factor) %>% 
  str()
#> 'data.frame':    150 obs. of  1 variable:
#>  $ Species: Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

some()和every(),返回T或者F,與any()、all()類似,用法不一樣;
detect()找到謂詞為true的第一個元素; detect_index()返回它的位置。

> keep(list("a", 1), is.character)
[[1]]
[1] "a"

> some(c("a", "b", 4), is.character)
[1] TRUE
> any(is.character(c("a", "b", 4)))
[1] TRUE

模型

library(tidyverse)
library(modelr)
options(na.action = na.warn)

htmlwidgets
有許多包提供htmlwidgets,包括:

dygraphs,http://rstudio.github.io/dygraphs/,用於互動式時間序列視覺化。

DT,http://rstudio.github.io/DT/,用於互動式表格。

threejs,https://github.com/bwlewis/rthreejs用於互動式三維圖。

DiagrammeR,http://rich-iannone.github.io/DiagrammeR/用於圖表(如流程圖和簡單的節點連結圖)。

networkD3,http://christophergandrud.github.io/networkD3/

要了解有關htmlwidgets的更多資訊並檢視提供它們的更完整的軟體包列表,請訪問http://www.htmlwidgets.org/。
leaflet: http://rstudio.github.io/leaflet/

leafletCN
https://blog.csdn.net/sinat_26917383/article/details/57083985

install.packages("leafletCN")
install.packages("rgeos")

R地圖

R+大地圖時代︱ leaflet/leafletCN 動態、互動式繪製地圖(遍地程式碼圖)
有中國各省市縣的地圖,使用非常方便,很容易得到一個不同顏色的中國地圖,不同資料對應不同顏色。