1 課程目標

  • 本次講義將介紹R的資料讀取、資料匯出、基本指令,也就是R可以讀取的外部資料,包括統計軟體、試算表軟體、編輯軟體、網路等等資料。請在你的R project所在的資料夾下面開一個新資料夾data,然後把資料存到data這個資料夾。例如讀取Stata資料:
  • #Library
    library(readstata13)
    #working path
    file <- here::here('data','Mystata.dta')
    #read dataset
    df1 <- read.dta13(file)
    #show the first five variables in the data
    str(df1[, 1:5])
    ## 'data.frame':    1244 obs. of  5 variables:
    ##  $ Q1: int  0 0 12 1 0 0 1 0 0 11 ...
    ##  $ Q2: int  1 1 2 2 2 2 2 2 2 1 ...
    ##  $ Q3: int  1 1 1 1 1 1 1 98 96 1 ...
    ##  $ Q4: int  2 2 1 3 1 4 4 98 3 3 ...
    ##  $ Q5: int  2 3 2 2 2 2 3 2 3 2 ...

    2 匯入資料

    2.1 scan()

  • scan()這個函數類似read.table,它可以讀取外部資料轉成向量,但是無法讀取表格,是一個處理簡單資料的指令,首先以數值資料舉例:
  • scan('~/Dropbox/EastAsia2024/data/voteshare', comment.char = '#', dec='.')

    [1] 55.6 66.1 36.8 65.1 50.9 44.9 48.7 52.4 48.5 53.0 51.9

  • 設定comment.char = ‘#’是告訴R把前面有’#’視為文字說明,不是資料。
  • 如果我們知道資料的變數性質,我們可以用scan()讀取。例如讀取一筆網路上的資料:
  • ucladat <- scan("https://stats.idre.ucla.edu/stat/data/scan.txt", 
                    what = list(age = 'numeric', name = ""))
    ndat <- data.table::setDF(ucladat)
    ndat
    ##   age    name
    ## 1  12   bobby
    ## 2  24    kate
    ## 3  35   david
    ## 4  20 michael
  • 可以看出scan()可以讀取外部的資料,但是這筆資料每一欄之間空一格,並沒有特別的符號區隔。
  • scan()的最佳功能是從R直接輸入資料。例如我們想要創造一筆資料稱為tmp,先指定兩個變數,第一個是公司名稱,第二個是目前市值(十億美元為單位):
  • tmp<-scan(what=list(company="character", 
                 marketvalue="numeric"))
  • 接下來手動輸入資料,輸入完10筆之後,按enter,就會結束:
  •      Apple, 851, 
         Alphabet, 719,
        Microsoft, 703, 
        Amazon, 701, 
        Tencent, 496,
        Berkshire Hathaway, 492, 
        Alibaba, 470,
        Facebook, 464, 
        Jpmorgan Chase, 375,
        Johnson & Johnson, 344
  • 我們會得到一個列表資料tmp,可以再用data.tablesetDT()函數轉為資料框。
  • tmp1 <- data.table::setDT(tmp)
    tmp1
  • 如果我們想要保留這筆資料,可以用write.table()寫到指定的路徑與檔案名稱:
  • marketvalue <- here::here('data', 'marketvalue.txt')
    write.table(tmp1, marketvalue)
  • 因為我們知道資料的變數性質,我們可以用scan()讀取。記得在資料中要加上雙引號在文字變數:
  • file <- here::here('data','tencompanies.txt')
    dt <-scan(file, comment.char = '#', 
                what=list(company="character", 
                 marketvalue="numeric"))
    ndt <- data.table::setDF(dt)
    ndt
    ##               company marketvalue
    ## 1               Apple         851
    ## 2            Alphabet         719
    ## 3           Microsoft         703
    ## 4              Amazon         701
    ## 5             Tencent         496
    ## 6  Berkshire Hathaway         492
    ## 7             Alibaba         470
    ## 8            Facebook         464
    ## 9      Jpmorgan Chase         375
    ## 10  Johnson & Johnson         344
  • 轉換為資料框之後,我們發現市值是文字類型。我們可以用mutate直接轉換為數值變數。
  • library(tidyverse)
    ndt <- ndt %>% mutate(marketvalue=as.numeric(marketvalue))
    str(ndt)
    ## 'data.frame':    10 obs. of  2 variables:
    ##  $ company    : chr  "Apple" "Alphabet" "Microsoft" "Amazon" ...
    ##  $ marketvalue: num  851 719 703 701 496 492 470 464 375 344
  • 從以上例子可以看出,scan()的功能比較簡單,只能讀取向量資料。而以下介紹的read.csv()或是read.table()的功能比較完備。
  • 2.2 匯入CSV 資料

  • RStudio(1.1.423)可以從File–>Import Dataset開啟Text, Excel, SPSS, Stata, SAS等格式的資料。其中Text檔案又分為base以及rdr,前者比較容易,後者則需要用到套件,但是可以控制分隔符號,大致上效果一樣。
  • 如果要用語法。首先,read.csv()可以讀取用csv格式儲存的資料,例如讀取台北市中山區的陳炳甫議員的議員配合款資料(來自於議員投票指南):
  • file <- here::here('data', 'councilor.csv')
    csv1<-read.csv(file, 
          header=TRUE, sep=",", fileEncoding = 'BIG5')
    head(csv1)
    ##   Year budget       unit contracter open
    ## 1 2015    676     水利處       台球  Yes
    ## 2 2016    673 新建工程處       茂盛  Yes
    ## 3 2016    270 新建工程處       冠君  Yes
    ## 4 2016    255 新建工程處       金煌  Yes
    ## 5 2016    235 新建工程處       聖鋒  Yes
    ## 6 2016    190 新建工程處       福呈   No
  • 指令中的header=TRUE表示第一列被認為是變數名稱,而sep規範分隔的符號,fileEncoding=BIG5則是將文字以BIG5編碼顯示中文。
  • R讓使用者控制資料中的字串是否視為因素資料,也就是用stringAsFactors控制:
  • path <- here::here('data', 'councilor.csv')
    csv2<-read.csv(path, 
                   header=TRUE, sep=",", 
                   fileEncoding = 'BIG5', 
                   stringsAsFactors = F)
  • 比較資料中的變數屬性,請輸入以下指令:
  • class(csv1$unit); table(csv1$unit)
    ## [1] "character"
    ## 
    ##     公園處 新建工程處     水利處 
    ##          1          8          1
    class(csv2$unit)
    ## [1] "character"
  • 可以看到前一個是因素(類別),後者則是文字。文字不能轉換成數字,但是因素可以根據類別對應轉換成數字。
  • 2.3 readr套件

    • readr 套件裡面也有read_csv的函數,但是無法處理中文的編碼。
    path <- here::here('data', 'tsaipopularity0921.csv')
    csv.tsai <- readr::read_csv(path, 
                   col_names = TRUE)
    head(csv.tsai)
    ## # A tibble: 6 × 2
    ##   Date    Tsai
    ##   <chr>  <dbl>
    ## 1 18-Mar  26.4
    ## 2 18-Jun  23  
    ## 3 18-Sep  23.6
    ## 4 18-Dec  21.6
    ## 5 19-Mar  30.4
    ## 6 19-Jun  44.6

    2.4 讀取中文資料的方法

  • 因為資料裡面的中文常常無法顯示在圖形。請先輸入以下兩行指令列出目前可以使用的字體:
    • install.packages(‘extrafont’); library(extrafont)
    • font_import(); fonts()
  • 如果系統內字型有限,請搜尋王漢宗字型或者其他字型,下載後自行安裝到字體簿或是控制台的字型。
  • 然後選擇其中的中文字型,即可顯示中文字型於圖形中,例如圖 2.1
  • font.add('LiSu', 'Lisu.ttf')
    barplot(table(csv1$unit), family='LiSu')
    \label{fonttest}字型測試

    Figure 2.1: 字型測試

  • 或者選擇其他中文字型,例如明體,利用ggplot2的繪圖功能畫圖,如圖 2.2
  • font.add("GenRyuMin2JP-R","GenRyuMin2-R.ttc")
    library(ggplot2)
    p<-ggplot(data=csv1, aes(x=factor(unit))) +
      geom_bar(stat="count") +
      theme(text=element_text(family="GenRyuMin2JP-R", size=12)) +
      labs(x='Unit')
    p 
    \label{fig:ggplot2fig}ggplot2例子

    Figure 2.2: ggplot2例子

  • csv格式常見於開放資料,不需要商用軟體就可以讀取,但是變數無法兼顧標記以及數值,需要相關的資料對照。
  • 2.5 文字資料(txt)

  • read.table()可以讀取用txt格式儲存的表格資料,該資料的欄位用空白區隔,例如:
  • file <- here::here("data", "Studentsfull.txt")
    students<-read.table(file, header=TRUE, sep="")
    head(students)
    ##         ID     Name Department Score Gender
    ## 1 10322011    Ariel  Aerospace    78      F
    ## 2 10325023    Becky    Physics    86      F
    ## 3 10430101     Carl Journalism    69      M
    ## 4 10401032  Dimitri    English    83      M
    ## 5 10307120  Enrique  Chemistry    80      M
    ## 6 10207005 Fernando  Chemistry    66      M
    • 如果欄位之間用逗號、分號區隔,請改設定sep=“,”,或者sep=“;”,即可正確讀取資料。

    2.5.1 readr套件

    • readr這個套件裡面有read_table這個指令,可以讀取txt格式的檔案,如果遇到用空格相隔變數的資料,可以這樣設定:
    file <- here::here('data', 'hsb2.txt')
    hsb2 <- readr::read_table(file, col_names = TRUE,
                     col_types = NULL, na = "NA", skip = 0, 
        comment = "")
    head(hsb2)
    ## # A tibble: 6 × 11
    ##   `"id"`  `"gender"` `"race"`     `"ses"` `"schtyp"` `"prog"` `"read"` `"write"`
    ##   <chr>        <dbl> <chr>        <chr>   <chr>      <chr>    <chr>    <chr>    
    ## 1 "\"1\""         70 "\"male\""   "\"whi… "\"low\""  "\"publ… "\"gene… 57       
    ## 2 "\"2\""        121 "\"female\"" "\"whi… "\"middle… "\"publ… "\"voca… 68       
    ## 3 "\"3\""         86 "\"male\""   "\"whi… "\"high\"" "\"publ… "\"gene… 44       
    ## 4 "\"4\""        141 "\"male\""   "\"whi… "\"high\"" "\"publ… "\"voca… 63       
    ## 5 "\"5\""        172 "\"male\""   "\"whi… "\"middle… "\"publ… "\"acad… 47       
    ## 6 "\"6\""        113 "\"male\""   "\"whi… "\"middle… "\"publ… "\"acad… 44       
    ## # ℹ 3 more variables: `"math"` <dbl>, `"science"` <dbl>, `"socst"` <dbl>
    • readr這個套件裡面還有read_delim這個指令,可以讀取txt格式的檔案,用分號相隔變數的資料,可以這樣設定:
    lambda <- here::here('data','lambda.txt')
    tmp <- readr::read_delim(lambda, delim = ';', show_col_types = F)
    tmp
    ## # A tibble: 5 × 4
    ##   `Rating of  Department` `Modal Category` `Right Guesses` `Wrong Guesses`
    ##   <chr>                   <chr>                      <dbl>           <dbl>
    ## 1 軍公教人員 (N=30)       "TVBS"                        15              15
    ## 2 私部門職員 (N=30)       " 三立"                       15              15
    ## 3 勞工 (N=25)             " 民視"                       15              10
    ## 4 農林漁牧 (N=10)         " 三立"                        8               2
    ## 5 總數(N=95)               <NA>                         53              42
    • 如果資料是以tab定位點鍵間隔,改用read_tsv讀取:
    fruit <- here::here("data","fruits.tsv")
    tmp <- readr::read_tsv(fruit)
    head(tmp)
    ## # A tibble: 6 × 6
    ##      ID Name    Age Fruits     Drink  Music  
    ##   <dbl> <chr> <dbl> <chr>      <chr>  <chr>  
    ## 1     1 John     22 Pear       Coffee K-Pop  
    ## 2     2 Alice    30 Strawberry Soda   Country
    ## 3     3 Ben      28 Banana     Soda   R&B    
    ## 4     4 Eve      35 Mango      Juice  R&B    
    ## 5     5 Mia      29 Durian     Coffee R&B    
    ## 6     6 Paul     38 Pear       Water  Country
    • readr套件中有一些資料檔,可以自行嘗試,例如:
    DELIM <- readr::read_delim(readr::readr_example('chickens.csv'),
                             delim=',')
    head(DELIM)
    ## # A tibble: 5 × 4
    ##   chicken                 sex     eggs_laid motto                               
    ##   <chr>                   <chr>       <dbl> <chr>                               
    ## 1 Foghorn Leghorn         rooster         0 That's a joke, ah say, that's a jok…
    ## 2 Chicken Little          hen             3 The sky is falling!                 
    ## 3 Ginger                  hen            12 Listen. We'll either die free chick…
    ## 4 Camilla the Chicken     hen             7 Bawk, buck, ba-gawk.                
    ## 5 Ernie The Giant Chicken rooster         0 Put Captain Solo in the cargo hold.

    2.6 統計資料

    • 以下分別介紹讀取Stata與SPSS資料的幾種方式:

    2.6.1 Stata

  • Stata本身可以儲存資料為csv檔或其他檔案,R有套件可以直接讀取。Stata的12版以前資料可以用foreign這個套件其中的read.dta()。例如UCLA的Institue for Digital Research and Education(idre)的資料:
  • library(foreign)
    ucladata<-c("https://stats.idre.ucla.edu/stat/data/test.dta")
    udata1<-read.dta(ucladata)
    head(udata1)
    ##    make   model mpg weight price
    ## 1   AMC Concord  22   2930  4099
    ## 2   AMC   Pacer  17   3350  4749
    ## 3   AMC  Spirit  22   2640  3799
    ## 4 Buick Century  20   3250  4816
    ## 5 Buick Electra  15   4080  7827
  • 如果讀取Stata 的13版以後的資料需要readstata13這個套件:
  • library(readstata13)
    file <- here::here('data', 'Mystata.dta')
    udata2<-read.dta13(file)
    str(udata2$Q1)
    ##  int [1:1244] 0 0 12 1 0 0 1 0 0 11 ...
  • convert.factors這個參數控制是否將變數的值轉為因素,如果不轉為因素,則維持為整數或者數值。
  • file <- here::here('data', 'Mystata.dta')
    udata3 <- readstata13::read.dta13(file, convert.factors=F)
    class(udata2$partyid); class(udata3$partyid)
    ## [1] "factor"
    ## [1] "integer"
    • 如果不轉為因素,產生次數分配時會看到數值。請各位試試看轉為因素時,次數分配表顯示什麼。
    udata3 %>% janitor::tabyl(partyid) %>% 
        janitor::adorn_totals()
    ##  partyid    n  percent
    ##        1  287 0.230707
    ##        2  246 0.197749
    ##        3    4 0.003215
    ##        4   21 0.016881
    ##        5    2 0.001608
    ##        6   54 0.043408
    ##        7  557 0.447749
    ##        9   73 0.058682
    ##    Total 1244 1.000000

    2.6.2 SPSS

  • foreign的套件也可以讀取SPSS的資料,使用read.spss()
  • library(foreign)
    file <- here::here('data', 'PP0797B2.sav')
    dv<-read.spss(file, 
                  use.value.labels=F, to.data.frame=TRUE)
    dv %>% janitor::tabyl(Q1) %>% 
          adorn_totals()
    ##     Q1    n  percent
    ##      1  617 0.299806
    ##      2  684 0.332362
    ##      3  443 0.215258
    ##      4   91 0.044218
    ##     95   10 0.004859
    ##     96   57 0.027697
    ##     97   52 0.025267
    ##     98  104 0.050534
    ##  Total 2058 1.000000
    • 設定use.value.labels=F表示讀取資料時並不會使用資料中原有的變數標記,例如低、中、高教育程度會變成 1、2、3。這樣做的好處是不必把類別變數轉換成數字,壞處則是需要對照原有的資料才能得知每一個值的意義。如果沒有設定 to.data.frame=T,讀取的資料會轉換成列表。請嘗試去掉use.value.labels=F,也就是read.spss()的內建值。

    • read.spss無法指定文字編碼方式。如果嘗試不同編碼得到的都是亂碼,請自行設定變數的標記。以Q1這個變數為例:

    dv$Q1n <-c()
    dv$Q1n[dv$Q1==1]<-'非常不同意'
    dv$Q1n[dv$Q1==2]<-'不同意'
    dv$Q1n[dv$Q1==3]<-'同意'
    dv$Q1n[dv$Q1==4]<-'非常同意'
    dv$Q1n=factor(dv$Q1n, levels=c('非常不同意','不同意','同意','非常同意'))
    par(bg='lightblue', family='HanWangWCL07')
    barplot(table(dv$Q1n), col='white')
    \label{fig:code}編碼標記圖形

    Figure 2.3: 編碼標記圖形

  • 另一個讀取 SPSS 資料的方法是先下載haven這個套件,然後用read_sav()函數來讀資料。這個方法並不會讀取資料中的中文標記,變數都是數值變數。
  • udata1<-haven::read_sav(file, encoding = 'UTF-8')
    udata1[1:4, 1:3]
    ## # A tibble: 4 × 3
    ##   Q1              Q2           Q3            
    ##   <dbl+lbl>       <dbl+lbl>    <dbl+lbl>     
    ## 1 96 [很難說]     3 [同意]     2 [不同意]    
    ## 2  1 [非常不同意] 4 [非常同意] 2 [不同意]    
    ## 3  1 [非常不同意] 4 [非常同意] 1 [非常不同意]
    ## 4  3 [同意]       3 [同意]     2 [不同意]
    pie(table(udata1$Q1))
    \label{haven}以haven套件讀取資料後的圓餅圖

    Figure 2.4: 以haven套件讀取資料後的圓餅圖

  • 第三個讀取 SPSS 資料的方法是先下載sjlabelled這個套件,然後用read_spss()函數來讀資料。這個方法可以讀取變量的中文標記,但是變數都是數值變數。有關sjlabelled的功能,請參考這個套件的作者–Daniel L\(\rm{\ddot{u}}\)decke的網頁
  • file <- here::here('data', 'PP1697C1.sav')
    udata4<-sjlabelled::read_spss(file)
    sjlabelled::get_labels(udata4$Q10)

    [1] “非常不同意” “不同意” “既不同意也不反對” “同意”
    [5] “非常同意” “拒答” “看情形” “無意見”
    [9] “不知道”

  • 如何用到變數中的標記呢?可以用sjlabelled這個套件的as_label函數,例如我們畫直方圖2.5顯示這個變數的分佈:
  • #set_labels(udata4$Q7, labels='總統滿意度')
    #            set_labels(udata4$Q8, labels='政治興趣')
    par(bg='#0022FF33')
    barplot(table(sjlabelled::as_label(udata4$Q8)), 
            col='white', family='YouYuan', cex.names=0.8)
    \label{sjlabel}以sjlabelled套件讀取資料後的直方圖

    Figure 2.5: 以sjlabelled套件讀取資料後的直方圖

  • 我們可以進行交叉分析如下表2.1
  • library(sjlabelled); library(knitr)
    library(kableExtra)
    
    crx<-table(as_label(udata4$Q8), as_label(udata4$Q7))
    kable(crx, format = 'pandoc', 
          caption = '政治興趣與總統滿意度') %>%
      kable_styling(bootstrap_options = "striped", full_width = F)
    Table 2.1: 政治興趣與總統滿意度
    非常不滿意 不太滿意 有點滿意 非常滿意 拒答 看情形 無意見 不知道
    完全沒興趣 97 92 71 7 7 5 27 29
    不太有興趣 95 150 122 14 5 11 38 25
    還算有興趣 55 69 84 16 2 2 10 4
    非常有興趣 12 10 14 9 0 1 1 2
    拒答 0 0 0 0 1 0 1 0
    看情形 9 3 1 2 2 0 1 0
    無意見 0 0 3 1 0 0 2 1
    不知道 0 1 0 0 0 0 1 1
  • 有了sjlabelled這個套件,可以方便地查看SPSS資料的變數標記以及選項數值,不用再跟SPSS資料對照。除此之外,這個套件還有很多功能。
  • 2.7 網路連結資料

  • read.table()可以讀取網路的連結資料,讓使用者方便下載分析。例如UCLA的idre的資料:
  • test.missing <- read.table(
      "https://stats.idre.ucla.edu/stat/data/test_missing_comma.txt",
        header = TRUE, sep = ",")
    head(test.missing)
    ##     prgtype gender  id ses schtyp level
    ## 1   general      0  70   4      1     1
    ## 2    vocati      1 121   4     NA     1
    ## 3   general      0  86  NA     NA     1
    ## 4    vocati      0 141   4      3     1
    ## 5  academic      0 172   4      2     1
    ## 6  academic      0 113   4      2     1
    college<-read.csv('https://stats.moe.gov.tw/files/detail/111/111_ab110_S.csv', header=TRUE)
    nrow(college)
    ## [1] 148
    • 畫圖 2.6顯示境外學生人數前五名的學校:
    showtext::showtext_auto()
    library(tidyverse)
    df<- college %>% mutate (degree=學位生_正式修讀學位外國生+
                         學位生_僑生.含港澳.+學位生_正式修讀學位陸生)
    newdf <- df[order(df$degree, decreasing=T), ]
    newdat<- data.frame(school=newdf$學校名稱[1:5],
                       degree=newdf$degree[1:5] )
    newdat$school<-factor(newdat$school, levels=newdf$學校名稱[1:5])
    ggplot(data=newdat, aes(x=school, y=degree)) +
         geom_bar(stat='identity', fill='#EECCEE') +
         theme(text=element_text(family='YouYuan', size=11))+
         theme_bw()
    \label{fig:degree}境外學位生人數前五名學校

    Figure 2.6: 境外學位生人數前五名學校

    • readrhavensjlabelledforeign這四個套件各有不同的讀取資料函數,有不同的功能,例如sjlabelled可以顯示變數的標記與名稱,readr適用於各種分隔方式的文字檔。請多多嘗試。

    3 資料匯出

  • R讓使用者處理資料之後輸出資料,讓其他使用者在其他平台使用。
  • write.table()可以匯出資料成為txt或是csv格式到指定的目錄,例如載入一個現有的檔案:
  • file <- here::here('data', 'voteshare')
    vs<-scan(file, comment.char = '#', dec='.')
    vs
  • 增加新的觀察值在vs資料中,然後匯出資料成為txt檔:
  • file <- here::here('data', 'voteshare')
    vs <- scan(file, comment.char = '#', dec='.')
    vsnew<-c(vs, 61.9, 31.8, 44.5)
    vsnew
    ##  [1] 55.6 66.1 36.8 65.1 50.9 44.9 48.7 52.4 48.5 53.0 51.9 61.9 31.8 44.5
    write.table(vsnew,'vsnew.txt')
    read.table('vsnew.txt')
    ##       x
    ## 1  55.6
    ## 2  66.1
    ## 3  36.8
    ## 4  65.1
    ## 5  50.9
    ## 6  44.9
    ## 7  48.7
    ## 8  52.4
    ## 9  48.5
    ## 10 53.0
    ## 11 51.9
    ## 12 61.9
    ## 13 31.8
    ## 14 44.5
  • 或者合併資料,並匯出資料為csv檔:
  • de<-data.frame(name=state.abb, region=state.region, area=state.area)
    region.a<-substr(state.region, 1,1)
    region.a
    ##  [1] "S" "W" "W" "S" "W" "W" "N" "S" "S" "S" "W" "W" "N" "N" "N" "N" "S" "S" "N"
    ## [20] "S" "N" "N" "N" "S" "N" "W" "N" "W" "N" "N" "W" "N" "S" "N" "N" "S" "W" "N"
    ## [39] "N" "S" "N" "S" "S" "W" "N" "S" "W" "S" "N" "W"
    de <- data.frame(de, region.short=as.factor(region.a))
    head(de)
    ##   name region   area region.short
    ## 1   AL  South  51609            S
    ## 2   AK   West 589757            W
    ## 3   AZ   West 113909            W
    ## 4   AR  South  53104            S
    ## 5   CA   West 158693            W
    ## 6   CO   West 104247            W
    write.csv(de, 'state.csv', row.names = F)
    state<-read.csv('state.csv', header=TRUE)
    head(state)
    ##   name region   area region.short
    ## 1   AL  South  51609            S
    ## 2   AK   West 589757            W
    ## 3   AZ   West 113909            W
    ## 4   AR  South  53104            S
    ## 5   CA   West 158693            W
    ## 6   CO   West 104247            W
  • 在合併變數成為一個資料框時,最好給定每一個欄位一個變數名稱,以方便日後分析。而在執行write.csv()時,不需要指定分隔的符號,在重新讀取時,也不需要刻意指定,仍然可以匯入正確的資料。
  • 4 常用指令

    4.1 管理環境空間

  • R有 global 這個環境空間中儲存命令列中所建立的任何變數,若要了解 global 環境空間有哪些物件,可以使用globalenv() 這個函數:
  • globalenv()

    <environment: R_GlobalEnv>

    ls(envir = globalenv(),10)

    [1] “college” “crx” “csv.tsai” “csv1” “csv2”
    [6] “de” “DELIM” “df” “df1” “dt”
    [11] “dv” “file” “fruit” “hsb2” “lambda”
    [16] “ndat” “ndt” “newdat” “newdf” “p”
    [21] “path” “region.a” “state” “students” “test.missing” [26] “tmp” “ucladat” “ucladata” “udata1” “udata2”
    [31] “udata3” “udata4” “vs” “vsnew”

    • ls()指令回傳在特定環境空間內的物件。
    • 以下介紹與環境空間有關的指令:
    1. attach():在工作環境中,可以把資料框、向量附加到搜尋的路徑,使得變數對R是直接可見的。但是attach無法儲存更改後的資料,因此要記得匯出資料,或者是用語法紀錄。例如:
    head(csv2)
    ##   Year budget       unit contracter open
    ## 1 2015    676     水利處       台球  Yes
    ## 2 2016    673 新建工程處       茂盛  Yes
    ## 3 2016    270 新建工程處       冠君  Yes
    ## 4 2016    255 新建工程處       金煌  Yes
    ## 5 2016    235 新建工程處       聖鋒  Yes
    ## 6 2016    190 新建工程處       福呈   No
    attach(csv2)
    contracter
    ##  [1] "台球"   "茂盛"   "冠君"   "金煌"   "聖鋒"   "福呈"   "盛吉"   "茂盛"  
    ##  [9] "冠君"   "未發包"
    contracter[1]<-"未發包"
    csv2$contracter[10]<-"台球"
    csv2
    ##    Year budget       unit contracter open
    ## 1  2015    676     水利處       台球  Yes
    ## 2  2016    673 新建工程處       茂盛  Yes
    ## 3  2016    270 新建工程處       冠君  Yes
    ## 4  2016    255 新建工程處       金煌  Yes
    ## 5  2016    235 新建工程處       聖鋒  Yes
    ## 6  2016    190 新建工程處       福呈   No
    ## 7  2015    155     公園處       盛吉  Yes
    ## 8  2016    154 新建工程處       茂盛  Yes
    ## 9  2016    142 新建工程處       冠君  Yes
    ## 10 2016    123 新建工程處       台球  Yes
    detach(csv2)
    csv2
    ##    Year budget       unit contracter open
    ## 1  2015    676     水利處       台球  Yes
    ## 2  2016    673 新建工程處       茂盛  Yes
    ## 3  2016    270 新建工程處       冠君  Yes
    ## 4  2016    255 新建工程處       金煌  Yes
    ## 5  2016    235 新建工程處       聖鋒  Yes
    ## 6  2016    190 新建工程處       福呈   No
    ## 7  2015    155     公園處       盛吉  Yes
    ## 8  2016    154 新建工程處       茂盛  Yes
    ## 9  2016    142 新建工程處       冠君  Yes
    ## 10 2016    123 新建工程處       台球  Yes
  • 上面的例子顯示,如果只是更改向量的元素,而不是更改資料框加上向量的元素,那麼並不會真正改變資料框的內容,而一旦更動,即使detach()該資料集,也會維持其變動。
    1. detach():從工作環境移除已經附加的資料框、向量,以避免混淆。
    2. rm(list=ls()):從工作環境移除所有的向量、列表、資料框等等。在執行新的語法之前,不妨先執行這一行指令,確定在目前環境中沒有其他的向量或者資料框。
    3. rm():刪除特定的向量、列表、資料框等等。
    4. save.image(‘.Rdata’):儲存環境空間內所有的資料與結果,下次打開.Rdata就不需要重新執行語法。
    5. load():下載.Rdata,載入所有資料與結果。
  • 綜合以上的指令,我們練習打開資料、執行統計模型、儲存結果等等。
  • rm(list=ls()) #remove all data
    data(mtcars) #suppose we analyze mtcars
    m1<-lm(mpg ~ cyl, data=mtcars) #regression
    summary(m1) #results
    ## 
    ## Call:
    ## lm(formula = mpg ~ cyl, data = mtcars)
    ## 
    ## Residuals:
    ##    Min     1Q Median     3Q    Max 
    ## -4.981 -2.119  0.222  1.072  7.519 
    ## 
    ## Coefficients:
    ##             Estimate Std. Error t value Pr(>|t|)    
    ## (Intercept)   37.885      2.074   18.27  < 2e-16 ***
    ## cyl           -2.876      0.322   -8.92  6.1e-10 ***
    ## ---
    ## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    ## 
    ## Residual standard error: 3.21 on 30 degrees of freedom
    ## Multiple R-squared:  0.726,  Adjusted R-squared:  0.717 
    ## F-statistic: 79.6 on 1 and 30 DF,  p-value: 6.11e-10
    mydata<-data.frame(date=as.Date(c("2018-03-13",
                                      "2018-03-14","2018-03-15"),
                                    format='%Y-%m-%d'), 
                       workinghours=c(4, 3, 4)) #create your own data
    
    save.image("test.Rdata") #save all results to Rdata
    rm(list=ls()) #remove all data
    load("test.Rdata") #load Rdata
    ls(envir = globalenv(),10) #display objects in this environment
    ## [1] "m1"     "mtcars" "mydata"
    mydata #diplay your data
    ##         date workinghours
    ## 1 2018-03-13            4
    ## 2 2018-03-14            3
    ## 3 2018-03-15            4
    1. saveRds():儲存成RDS
      saveRDS()可以儲存單一的物件。如果不想儲存原來的物件名稱,也可以考慮saveRDS()
    newfile <- here::here('data','voteshare')
    vs<-scan(newfile, comment.char = '#', dec='.')
    vs
    ##  [1] 55.6 66.1 36.8 65.1 50.9 44.9 48.7 52.4 48.5 53.0 51.9
    vs2<-vs/100
    saveRDS(vs, "vs.rds")
    saveRDS(vs2, 'vs2.rds')
    rm(vs); rm(vs2)
    vs<-readRDS('vs.rds')
    vs2<-readRDS('vs2.rds')
    vs; vs2
    ##  [1] 55.6 66.1 36.8 65.1 50.9 44.9 48.7 52.4 48.5 53.0 51.9
    ##  [1] 0.556 0.661 0.368 0.651 0.509 0.449 0.487 0.524 0.485 0.530 0.519
  • saveRDS()的優點是雖然一次只儲存一個物件,但是可以避免像save.image()把新的物件蓋過舊的物件,新舊物件可以並存。
  • 4.2 程式相關函式

    • print():顯示資料框、向量、列表等等,但是無法附加上文字。
    • source()R可以讀取既有指令的檔案,在不必開啟命令稿的情況下直接執行多行程式,可節省許多篇幅以及時間。例如我們寫一個自訂函數,語法很長,我們先存成一個語法檔,未來可以直接執行。
    sink("twohistograms.R") #define a new script file
    cat("set.seed(02138)") #input a function that sets starting number for random number
    cat("\n") #end of line
    cat("#write R script to a file without opening a document")
    cat("\n")  #end of line
     cat("fnorm<-function(mu){            #create a function with a parameter: mu
          sample.o<-rnorm(20,mu,1/sqrt(mu))  #define the 1st vector that generates random numbers
          sample.i<-sample.o+runif(1,0,10)  #define the 2nd vector that generates random numbers
          par(mfrow=c(1,2))                 #set parameter of graphic for 1*2 graphics
          hist(sample.o, col=1, main='',    #histogram with Basic R
                    xlab='Original sample')
                 hist(sample.i, col=4, main='', #another histogram
                  xlab='Original sample + random number')
          }")
     cat("\n")  #end of function
     sink()     #save the script in the specified file
    file.show("twohistograms.R") #Opening an editor to show the script
  • 我們建立 fnorm()這個函數,並且存成一個語法檔(“twohistograms.R”),並且用file.show()顯示出來。以後就可以執行它。
  • 使用source()函數,執行”twohistograms.R”此一語法檔,產生一個自訂函數,然後輸入參數便可顯示結果。請執行上面的指令之後,自行輸入以下兩行語法:
  • source("twohistograms.R")
    fnorm(1)
  • 如果執行成功會看到以下圖形:
  • 確定一下工作目錄的確多了”twohistograms.R”此一語法檔。
    • with():當環境空間有一個以上的資料框,為了避免混淆,可以使用該指令進行分析:
    par(mfrow=c(1,2))
    library(car)
    with(Duncan, hist(income, col=2))
    with(Salaries, hist(salary, col=6))
    \label{twohist}兩個變數名稱相似的長條圖

    Figure 4.1: 兩個變數名稱相似的長條圖

  • 注意,該指令不適用於矩陣,例如state.x77。
  • 4.3 資料相關的函式

    • names():顯示資料框的變數名稱,例如:
    names(mtcars)
    ##  [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
    ## [11] "carb"
  • 注意,該函式不適用於矩陣,例如state.x77。
    • which():顯示特定變數。例如,哪些樹的圓周符合條件:
    which(Orange$circumference>100)
    ##  [1]  4  5  6  7 10 11 12 13 14 18 19 20 21 24 25 26 27 28 32 33 34 35
  • 看起來有相當多的樹木胸圍超過100公釐(10公分),但是到底有哪些樹符合這個條件?可應用which()函數加以篩選:
  • oc<-which(Orange$circumference>100) #create a vector 
                        #of data that meets a condition
    oc
    ##  [1]  4  5  6  7 10 11 12 13 14 18 19 20 21 24 25 26 27 28 32 33 34 35
    Orange[oc,] #match data with the vector
    ##    Tree  age circumference
    ## 4     1 1004           115
    ## 5     1 1231           120
    ## 6     1 1372           142
    ## 7     1 1582           145
    ## 10    2  664           111
    ## 11    2 1004           156
    ## 12    2 1231           172
    ## 13    2 1372           203
    ## 14    2 1582           203
    ## 18    3 1004           108
    ## 19    3 1231           115
    ## 20    3 1372           139
    ## 21    3 1582           140
    ## 24    4  664           112
    ## 25    4 1004           167
    ## 26    4 1231           179
    ## 27    4 1372           209
    ## 28    4 1582           214
    ## 32    5 1004           125
    ## 33    5 1231           142
    ## 34    5 1372           174
    ## 35    5 1582           177
  • oc是滿足樹的圓周超過100公釐的觀察值,而以該資料框配對這些觀察值,只留下可以配對的每一列觀察值。
    • rep(A, n):重複A數值或者字串n次
    rep(3, 5)
    ## [1] 3 3 3 3 3
    c(rep("大", 3), rep("中", 1), rep("小",2))
    ## [1] "大" "大" "大" "中" "小" "小"
    • seq(i,j):傳回i到j的連續數字
    seq(1,10)
    ##  [1]  1  2  3  4  5  6  7  8  9 10
    seq(100,110, by=2)
    ## [1] 100 102 104 106 108 110
    • seq(i:j):傳回i到j的順位數字
    seq(5:10)
    ## [1] 1 2 3 4 5 6
    seq(100:110)
    ##  [1]  1  2  3  4  5  6  7  8  9 10 11

    4.4 文字相關的函式

    • grep():傳回字串向量或資料中符合條件的元素或所在的列。例如我們有一個字串是拉脫維亞的城市名稱,我們想知道哪幾個城市有pils這幾個字:
    latvija<-c("Daugavpils","Jēkabpils","Jelgava
               Liepāja","Rēzekne","Rīga","Valmiera",
               "Ventspils")
    grep("pils", latvija)
    ## [1] 1 2 7
    latvija[grep("pils", latvija)]
    ## [1] "Daugavpils" "Jēkabpils"  "Ventspils"
  • 還記得之前使用的政府開放資料嗎?假設我們想篩選出「區」的資料,可以運用\(\texttt{grep()}\)回傳在code欄位有「區」的列:
  • opendata <- here::here("data","opendata106N0101.csv")
    dat <- readr::read_csv(opendata, col_names = TRUE)
    district<-dat[grep("區", dat$code), ]
    head(dat, n=3)
    ## # A tibble: 3 × 4
    ##   code         年底人口數 土地面積 人口密度
    ##   <chr>        <chr>         <dbl> <chr>   
    ## 1 新北市板橋區 551480         23.1 23835   
    ## 2 新北市三重區 387484         16.3 23747   
    ## 3 新北市中和區 413590         20.1 20532
  • 以上可以應用在列表資料,假設我們有一筆資料是電視頻道的屬性,可以篩選出:
  • #create a list 
     L <- list(a<-c('lecture', 'movie'), b<-c('Movie channel'), c=c(1:10),
               d<-c('movie','food', "news",'car','music'))
    #select elements in L
     match.s<-grep('movie', L)  ; match.s 
    ## [1] 1 4
    #subset of L
     L[grep('movie', L)]
    ## [[1]]
    ## [1] "lecture" "movie"  
    ## 
    ## [[2]]
    ## [1] "movie" "food"  "news"  "car"   "music"
    • gsub():取代符合條件的字串。以上述為例,假設我們想把「臺」一律改為「台」,則可以這樣做:
    library(tidyverse)
    #dat2 <-dat[grep("臺", dat$code), ]
    #change 臺 to  台
    dat2 <- dat%>% mutate(code=gsub("臺", "台", dat$code))
    #subset
    dat2[grep('台北市', dat2$code), ] 
    ## # A tibble: 12 × 4
    ##    code         年底人口數 土地面積 人口密度
    ##    <chr>        <chr>         <dbl> <chr>   
    ##  1 台北市松山區 206988         9.29 22286   
    ##  2 台北市信義區 225753        11.2  20143   
    ##  3 台北市大安區 309969        11.4  27283   
    ##  4 台北市中山區 230710        13.7  16862   
    ##  5 台北市中正區 159608         7.61 20981   
    ##  6 台北市大同區 129278         5.68 22754   
    ##  7 台北市萬華區 191850         8.85 21673   
    ##  8 台北市文山區 274424        31.5  8709    
    ##  9 台北市南港區 122155        21.8  5593    
    ## 10 台北市內湖區 287771        31.6  9113    
    ## 11 台北市士林區 288295        62.4  4622    
    ## 12 台北市北投區 256456        56.8  4513
    • substr():擷取符合起始與結束字元的字串。例如在上述資料中,我們想建立一個縣市的類別變數:
    #Open data
    opendata <- here::here("data","opendata106N0101.csv")
    dat <- readr::read_csv(opendata, col_names = TRUE)
    #create a new variable from 'code'
    dat2 <- dat%>% dplyr::mutate(city=substr(dat$code, 1,3))
    head(dat2, n=3)
    ## # A tibble: 3 × 5
    ##   code         年底人口數 土地面積 人口密度 city  
    ##   <chr>        <chr>         <dbl> <chr>    <chr> 
    ## 1 新北市板橋區 551480         23.1 23835    新北市
    ## 2 新北市三重區 387484         16.3 23747    新北市
    ## 3 新北市中和區 413590         20.1 20532    新北市
  • 以下練習取出各個鄉鎮市區所屬的縣市,去掉東沙、南沙群島,排序,然後按照縣市的土地面積大小順序畫圖:
  • dat2 <- dat2[-c(371:375),]
    dat3 <- dat2 %>%
         dplyr::group_by(city) %>%
         dplyr::summarize(avg.area=mean(土地面積, na.rm = T),
                          sum.area=sum(土地面積, na.rm = T)) %>% 
          dplyr::filter(sum.area >3)
    
    ggplot2::ggplot(data=dat3, aes(y=sum.area, 
            x=reorder(city, -sum.area))) +
            geom_point() +
            theme(axis.text = element_text(family="HanWangYanKai", size=7),
                  axis.title = element_text(family="Georgia", size=14)) +
            xlab("City") +
            ylab("Area")
    \label{fig:cityarea}各縣市土地面積

    Figure 4.2: 各縣市土地面積

    \(\blacksquare\)請練習畫圖表示各縣市的人口數統計(提示,用轉換字串的年底人口數變成數值)

    • sub()gsub():取代指定的字串,例如:
    country<-c( "United States", "Republic of Kenya", "Republic of Korea")
    sub('Republic of', '', country)
    ## [1] "United States" " Kenya"        " Korea"
  • 因為gsub()會替換所有符合條件的字串,所以比sub()好用,例如:
  • U<-matrix(c('文殊蘭花與蝴蝶蘭花','茶花','杜鵑花',
                 '玫瑰花','菊花','蘭花'), nrow=3, ncol=2)
    U
    ##      [,1]                 [,2]    
    ## [1,] "文殊蘭花與蝴蝶蘭花" "玫瑰花"
    ## [2,] "茶花"               "菊花"  
    ## [3,] "杜鵑花"             "蘭花"
    sub('蘭花','蘭', U)
    ##      [,1]               [,2]    
    ## [1,] "文殊蘭與蝴蝶蘭花" "玫瑰花"
    ## [2,] "茶花"             "菊花"  
    ## [3,] "杜鵑花"           "蘭"
    gsub('蘭花','蘭', U)
    ##      [,1]             [,2]    
    ## [1,] "文殊蘭與蝴蝶蘭" "玫瑰花"
    ## [2,] "茶花"           "菊花"  
    ## [3,] "杜鵑花"         "蘭"
  • 有時候我們會遇到一些特殊符號,需要一點特殊技巧除去這些符號,例如:
  • zodiac<-c( "(mouse)", "(ox)", "(tiger)", "(rabbit)", "(dragon)")
    zodiac<-sub("\\(","", zodiac)
    sub("\\)","", zodiac)
    ## [1] "mouse"  "ox"     "tiger"  "rabbit" "dragon"
  • 回到剛剛國家名稱的例子:
  • country<-c( "United States", "Republic of Kenya", "Republic of Korea")
  • 如果我們增加了其他國家,而且要去掉特定的字串,例如去掉”Republic of”,上面的指令要加上^或是$在指定的字串前面或後面,確定我們不會選到有其他文字的字串,例如:
  • country<-c("People's Republic of China
               Democratic Republic of Congo", 
               "United States",
    "Republic of Kenya", "Republic of Korea", 
    "Democratic People's Republic of Korea")
    country[grep('^Republic of', country)]
    ## [1] "Republic of Kenya" "Republic of Korea"
  • 如果我們想刪掉”Republic of”,可以這樣做:
  • gsub("^Republic of", "", country)
    ## [1] "People's Republic of China\n           Democratic Republic of Congo"
    ## [2] "United States"                                                      
    ## [3] " Kenya"                                                             
    ## [4] " Korea"                                                             
    ## [5] "Democratic People's Republic of Korea"
  • 這個表示方式叫做正規表示式。對於其他設定有興趣的同學可參考陳鍾誠的網頁
    • strsplit()是能夠將一個文字切割成向量的函式,例如:
    a <- c("Every day, Customs and Border Protection agents 
           encounter thousands of illegal immigrants trying 
           to enter our country. We are out of space to hold 
           them, and we have no way to promptly return them 
           back home to their country. America proudly 
           welcomes millions of lawful immigrants who enrich
           our society and contribute to our nation, but all
           Americans are hurt by uncontrolled illegal migration.")
    strsplit(a, split=" ")

    [[1]] [1] “Every” “day,” “Customs” “and” “Border”
    [6] “Protection” “agents” “” “” “”
    [11] “” “” “” “” “encounter”
    [16] “thousands” “of” “illegal” “immigrants” “trying”
    [21] “” “” “” “” “”
    [26] “” “” “to” “enter” “our”
    [31] “country.” “We” “are” “out” “of”
    [36] “space” “to” “hold” “” “”
    [41] “” “” “” “” “”
    [46] “them,” “and” “we” “have” “no”
    [51] “way” “to” “promptly” “return” “them”
    [56] “” “” “” “” “”
    [61] “” “” “back” “home” “to”
    [66] “their” “country.” “America” “proudly” “”
    [71] “” “” “” “” “”
    [76] “” “welcomes” “millions” “of” “lawful”
    [81] “immigrants” “who” “enrich” “” “”
    [86] “” “” “” “” “our”
    [91] “society” “and” “contribute” “to” “our”
    [96] “nation,” “but” “all” “” “”
    [101] “” “” “” “” “Americans”
    [106] “are” “hurt” “by” “uncontrolled” “illegal”
    [111] “migration.”

  • 分割文本為個別的字串後,就可以計算有興趣的文字出現幾次。
    • cat():顯示向量以及運算結果,並可以加上文字,並且用”斜線n”參數換行:
    x<-c(2,4,6)
    cat(x, "\n");
    ## 2 4 6
    cat("summation:", sum(x), "\n", "average:", mean(x))
    ## summation: 12 
    ##  average: 4
    • paste0()以及paste():回傳向量結合的結果,只是連接的方式不同,例如:
    library(dplyr)
    x1 = starwars$mass[1:6]
    weight <- paste(x1, "kg", sep=" ") 
    x2 = starwars$height[1:6]
    height <- paste0(x2, " ", "cm") 
    data.table::data.table(name=starwars$name[1:6], weight, height)
    ##              name weight height
    ##            <char> <char> <char>
    ## 1: Luke Skywalker  77 kg 172 cm
    ## 2:          C-3PO  75 kg 167 cm
    ## 3:          R2-D2  32 kg  96 cm
    ## 4:    Darth Vader 136 kg 202 cm
    ## 5:    Leia Organa  49 kg 150 cm
    ## 6:      Owen Lars 120 kg 178 cm

    5 置換資料的apply家族

    head(faithful)
    ##   eruptions waiting
    ## 1     3.600      79
    ## 2     1.800      54
    ## 3     3.333      74
    ## 4     2.283      62
    ## 5     4.533      85
    ## 6     2.883      55
    apply(faithful,2,function(x) c(min(x),max(x),
                                     mean(x),length(x)))
    ##      eruptions waiting
    ## [1,]     1.600    43.0
    ## [2,]     5.100    96.0
    ## [3,]     3.488    70.9
    ## [4,]   272.000   272.0
      dataapp<-read.table(text="Q1 Q2 Q3 Q4
                          1980 70 55 60 70
                          1990 60 70 55 69
                          2000 80 50 90 66
                          2010 80 60 70 88",header=T)
    apply(dataapp, 1, mean)
    ##  1980  1990  2000  2010 
    ## 63.75 63.50 71.50 74.50
    drink<-c(rep("coffee",14), rep("juice",10), 
               rep("coffee",5), rep("soda",13),
               rep("juice",10),rep("soda",8))
    heart<-c(rep("healthy", 21), 
             rep("not healthy",6), 
             rep("not healthy", 7), 
             rep("healthy",11), 
             rep("refuse to answer",15))
    tdh<-table(drink,heart); tdh
    ##         heart
    ## drink    healthy not healthy refuse to answer
    ##   coffee      14           5                0
    ##   juice       10           3                7
    ##   soda         8           5                8
    apply(tdh,1,sum)
    ## coffee  juice   soda 
    ##     19     20     21
      apply(tdh,2,sum)
    ##          healthy      not healthy refuse to answer 
    ##               32               13               15
    row.margin<-apply(tdh,1,function(x) 
        100*sum(x)/length(drink))
    row.margin<-round(row.margin,1)
    col.margin<-apply(tdh,2,function(x) 
      100*sum(x)/length(heart))
    col.margin<-round(col.margin,1)
    tdh<-cbind(tdh, row.margin)
    tdh<-rbind(tdh, col.margin=c(col.margin,100))
    tdh
    ##            healthy not healthy refuse to answer row.margin
    ## coffee        14.0         5.0                0       31.7
    ## juice         10.0         3.0                7       33.3
    ## soda           8.0         5.0                8       35.0
    ## col.margin    53.3        21.7               25      100.0
      apply(dataapp, 2, function(x) sum(x^2))
    ##    Q1    Q2    Q3    Q4 
    ## 21300 14025 19625 21761

    5.1 tapply

    • 在社會科學中我們經常遇到連續性的資料,而且必須以某個類別變數進行統計,例如每一個班級學生的數學成績、每一個年齡層的死亡率等等,像VADeaths 便是每一年齡層的死亡統計結果。當然,我們可以用subset()這個指令將資料分組然後進行統計,不過tapply() 可以更容易地得到相同的結果。以sleep 這筆資料為例:
    table(sleep$group)
    ## 
    ##  1  2 
    ## 10 10
    tapply(sleep$extra,sleep$group,mean)
    ##    1    2 
    ## 0.75 2.33
    • 以下是自建的函數:
    tapply(sleep$extra,sleep$group,function(x) sum(x^2))
    ##     1     2 
    ## 34.43 90.37
    • 由上例可以看出tapply()的第一個參數是我們想要分組的資料,第二個參數是分組變數,第三個則是函數。函數可以像 tapply()一樣自創,此處是計算平均值以及平方和。

    • tapply()允許超過一個的分組變數,例如用travel 這筆資料計算在grp以及gender 2$$2 分組下的 travel平均數:

    file <- here::here('data', 'fruits.tsv')
    dt <- readr::read_tsv(file)
    tr <- tapply(dt$Age, dt$Music, mean)
    tr
    ## Country   K-Pop     R&B    Rock 
    ##   31.88   25.60   28.50   29.44

    5.2 lapply

    • lapply()使用的時機是針對列表或者資料框進行統計,結果會呈現列表形態的結果。例如:
    x <- list(a = sample(1:100, 10), beta = exp(-3:3), logic = c(TRUE,FALSE,FALSE,TRUE))
    # compute the list mean for each list element
    lapply(x, mean)
    ## $a
    ## [1] 47
    ## 
    ## $beta
    ## [1] 4.535
    ## 
    ## $logic
    ## [1] 0.5
    • 或者應用在資料。我們把數字變數挑選出來,然後統計每個變數的分位:
    ISLR::Auto %>% select_if(is.numeric) %>% 
    lapply(quantile)
    ## $mpg
    ##    0%   25%   50%   75%  100% 
    ##  9.00 17.00 22.75 29.00 46.60 
    ## 
    ## $cylinders
    ##   0%  25%  50%  75% 100% 
    ##    3    4    4    8    8 
    ## 
    ## $displacement
    ##    0%   25%   50%   75%  100% 
    ##  68.0 105.0 151.0 275.8 455.0 
    ## 
    ## $horsepower
    ##    0%   25%   50%   75%  100% 
    ##  46.0  75.0  93.5 126.0 230.0 
    ## 
    ## $weight
    ##   0%  25%  50%  75% 100% 
    ## 1613 2225 2804 3615 5140 
    ## 
    ## $acceleration
    ##    0%   25%   50%   75%  100% 
    ##  8.00 13.78 15.50 17.02 24.80 
    ## 
    ## $year
    ##   0%  25%  50%  75% 100% 
    ##   70   73   76   79   82 
    ## 
    ## $origin
    ##   0%  25%  50%  75% 100% 
    ##    1    1    1    2    3
    • 要注意的是lapply不能同時進行兩種統計。
    • 我們也可以自己設定函數,應用到我們要分析的資料,例如我們創造一個平均離散程度(\(\frac{\sum (X^2-\bar{X})}{N}\))的函數,然後計算一筆資料中所有數字變數的平均離岸程度:
    mean_squared_dev <- function(x) sum(x^2 - mean(x))/length(x)
    carData::Duncan %>% select_if(is.numeric) %>% 
    lapply(mean_squared_dev)
    ## $income
    ## [1] 2295
    ## 
    ## $education
    ## [1] 3576
    ## 
    ## $prestige
    ## [1] 3197

    5.3 sapply

    • sapply() 得到的結果是帶有名稱的向量,例如:
    st <- carData::Duncan %>% select_if(is.numeric) %>% 
     sapply(mean)
    st
    ##    income education  prestige 
    ##     41.87     52.56     47.69
    class(st)
    ## [1] "numeric"
    • 列表的好處是變數可以有不同的長度,例如 datalist3這筆列表資料,每一個變數有不同的長度:
    datalist3<-list(dep=c(rep(1,30),rep(2,30),
                  rep(3,30), rep(4,60), rep(5,40)),
                  x1=c(rep(1,35),rep(0,80)), 
                  x2=c(rep("low",2),rep("middle",3),
                           rep("high",11)))
    dep<-datalist3[["dep"]]
    length(dep)
    ## [1] 190
    • 試著用sapply 計算 datalist3 其中三個變數的長度:
    sapply(datalist3, length)
    ## dep  x1  x2 
    ## 190 115  16
    • 以及平均值:
    sapply(datalist3, mean)
    ##    dep     x1     x2 
    ## 3.2632 0.3043     NA
    • 這裡要注意的是dplyr::select()不能處理列表資料,所以我們必須要用基礎的功能。

    • 接下來我們試著刪掉最後一個變數,然後重新計算平均數以及 25、75 百分位:

    dl13<-datalist3[-length(datalist3)]
    sapply(dl13, mean)
    ##    dep     x1 
    ## 3.2632 0.3043
    sapply(dl13, function(x) quantile(x,c (.25,.75)))
    ##     dep x1
    ## 25%   2  0
    ## 75%   4  1
    • datalist3的最後一個變數是字串,所以嘗試用 nchar() 做為 lapply() 的函數:
    dl2<-datalist3[-c(1,2)]
    nchar_x2<-lapply(dl2, nchar)
    nchar_x2[[1]][1:6]
    ## [1] 3 3 6 6 6 4
    • 從上面的介紹可以看出,應用函數大致上可以分成一般資料以及列表資料兩種,可以使用公用以及自訂函數進行分析。

    • 我們可以結合select以及sapply以篩選變數,例如:

    fruits <- here::here("data","fruits.tsv")
    tmp <- readr::read_tsv(fruits, show_col_types = FALSE)
    
    .f <- tmp %>% select(which(sapply(., class)=="character"))
    head(.f)
    ## # A tibble: 6 × 4
    ##   Name  Fruits     Drink  Music  
    ##   <chr> <chr>      <chr>  <chr>  
    ## 1 John  Pear       Coffee K-Pop  
    ## 2 Alice Strawberry Soda   Country
    ## 3 Ben   Banana     Soda   R&B    
    ## 4 Eve   Mango      Juice  R&B    
    ## 5 Mia   Durian     Coffee R&B    
    ## 6 Paul  Pear       Water  Country
    • sapply(., class)==裡面的「.」指的是前面提到的資料框。又例如:
    UsingR::dowdata %>% sapply(., mean)
    ##  Date  Open  High   Low Close 
    ##    NA 10609 10777 10448 10609

    6 作業

    請寫語法完成作業,每一條語法前面請用#說明該語法的意義(中英文皆可),並且顯示執行語法的結果。

    1. 請匯入這筆ire的資料hsb2_small(“https://stats.idre.ucla.edu/stat/data/hsb2_small.csv”),並且顯示該資料的變數名稱。

    2. 請使用site=“http://faculty.gvsu.edu/kilburnw/nes2008.RData” 以及load(file=url(site))。由以上指令讀取資料後,請先列出V083097的分佈。然後把這個變數重新編碼為「民主黨」(Democrat)、「共和黨」(Republican)、「獨立」(Independent)、「其他政黨」(Other party (SPECIFY)),然後列出這個變數的次數分配。

    3. 請匯出hsb2_small的資料為Text格式以及rds格式。

    4. 請匯入2008年的總統選舉資料(2008Election.csv),並且找出國民黨得票率最高的town.id。(提示:最大值的函數為max()

    5. 請嘗試匯入本週課程所使用的studentsfull檔案,但是這一次用read_table()

    6. 請列出政府開放資料opendata106N0101.csv中的大安區的部分資料。

    7. 請將Studentsfull.txt這筆資料中的Journalism改為Communication,並且顯示修改後屬於Communication的資料。

    8. 請問以下文字之中,有多少重複的字?

    Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we can not dedicate—we can not consecrate—we can not hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom—and that government of the people, by the people, for the people, shall not perish from the earth.
    1. 請讀取來自Gareth James的網站Resources - Second Edition — An Introduction to Statistical Learning (statlearning.com)中的資料連結 (https://www.statlearning.com/s/Advertising.csv) 的資料,並且顯示變數名稱與性質。

    2. 某同學有如下的資料,

    db <- tibble(salary=c('42,000','55,000','45,000','66,000', '65,000'), 
                 years=c(3,4,3,5,5), bonus=c(5000,4000,5000,6000,5000))
    db
    ## # A tibble: 5 × 3
    ##   salary years bonus
    ##   <chr>  <dbl> <dbl>
    ## 1 42,000     3  5000
    ## 2 55,000     4  4000
    ## 3 45,000     3  5000
    ## 4 66,000     5  6000
    ## 5 65,000     5  5000

    請幫忙他去除第一個變數的千位符號。

    7 更新講義時間

    最後更新時間: 2025-03-10 20:30:48