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 動態、互動式繪製地圖(遍地程式碼圖)
有中國各省市縣的地圖,使用非常方便,很容易得到一個不同顏色的中國地圖,不同資料對應不同顏色。