第 3 章 R Basics

參考資料

3.1 Packages

library(googlesheets4)

3.1.1 使用

library(googlesheets4)
若出現如下錯誤訊息,表示得先安裝相關套件:

Error in library(googlesheets4) : there is no package called ‘googlesheets4’

package若有成功library進來它會出現在:

3.1.2 視窗點選

以同時安裝dplyr及lubridate為例

3.1.3 指令

只安裝googlesheets4:

install.packages(c("googlesheets4"))

安裝googlesheets4及tidyverse:

install.packages(c("googlesheets4","tidyverse"))

3.2 Create an object with values

這章我們學習如何創造R裡帶有資訊的物件,在R裡創造方式為:

ObjectName <- object_value

object_value -> ObjectName
a <- 2
a = 2 # = 與 <-同義

3 -> b

#以後的文字均不會被當程式執行:

  • 適合用來進行程式註解。

  • 取消某行程行執行可將#放在一行開頭。

注意到右上角的Environment tab會顯示剛創的兩個objects及其values.

A valid variable name (also called symbol) consists of:

  • letters, numbers and the dot (.) or underline (_) characters.

A variable name starts with:

  • a letter;
  • or the dot not followed by a number.

下面幾個可以被R接受:

my_108_total_credits <- 15
_108_total_credits <- 15
108_total_credits <- 15
_my_108_total_credits <- 15
my.108.total_credits <- 15
.108.total_credits <- 15 # 隱藏變數
.my.108.total_credits <- 15
我的108學年總學分數 <- 15
`我的108學年總學分數` <- 15 # 特殊名稱處理,`不是名稱的一部份
`.108.total_credits` <- 15

一個chunk內若想:

  • 只執行某一行, 游標放在該行任意處,按Ctrl+Enter (Mac: Command+Enter)。

  • 只執行某一塊, 滑鼠按住左鍵選擇該區塊放開後,按Ctrl+Enter (Mac: Command+Enter)。

常見命名方式:

  • snake: my_total_credits <- 15

  • camel: myTotalCredits <- 15

3.3 Calling an object

物件名稱寫在物件值一側時,會是在呼叫變數

a <- 15 # 變數設定
b <- a # 呼叫a的值,用來設定變數b的值,相當於 b <- 15
c <- y

要呼叫某物件時,請先確認Environment tab裡有該物件存在,或執行ls()

在R裡使用物件呼叫來定義新物件並不會將兩者的值綁在一起:

a <- 3
b

在變數設定以外的情境,以變數名稱設定時之寫法寫下變數名稱時,也會是在呼叫變數

a+b

建立一個變數名稱為我的年紀,其值為20。另外建立一個變數名稱為my_age,其值以呼叫我的年紀變數取得。

3.4 Atomic Vector

R對每個元素都當成向量看待,即使

myName <- "Mary"
length(myName) # 查看物件本身代表幾個值。

在Python裡這個答案會是4。

c(...)將「相同類型」值以「逗點」分隔而形成的向量:

c(value1, value2, value3)

c為concatenate(堆疊)。

  • value1, …, value3: 為向量裡的 元素(element)

  • 整個向量形成一個值可以用來指定給物件名稱。

num1 <- 5
vNum1 <- c(2,5)
vNum2 <- c(2, num1)

只有一個值的向量可以不寫c()

以下兩變數值相同:

num1 <- 5
num2 <- c(5)

向量的堆疊依然是向量:

vNum1 <- c(-1,5)
vNum2 <- c(2,3)
vNum3 <- c(vNum1,vNum2)
vNum4 <- c(-1,5,2,3)
vNum5 <- c(c(-1,5),c(2,3))
vNum1 <- c(-1,5)
vNum2 <- c(2,3)
vNum4 <- c() # 空向量

請問以下操作後的變數(vNum3, vNum4)值為何?

vNum3 <- c(vNum2, vNum1)
vNum4 <- c(vNum4, vNum1) # 向量疊代 (recursive vector concatenate)

若反覆操作,它們會有什麼變化?

Atomic vector依其值的型態(type)區分,常見有以下三類型:

  • Numeric
  • Character
  • Logical

3.4.1 Numeric

num1<-5 # 非整數
num2<-5L # 整數

num1Vector<-c(5,6,7)
num2Vector<-c(5L,6L,7L)
  • 5會被電腦記成5.0000

  • 5L才會被當成整數5

object.size(num1)
object.size(num2)
object.size(num1Vector)
object.size(num2Vector)

3.4.2 Character/String (vector)

在定義object value時,必需要用「成對」的雙引號"或單引號',把每個字串括起來。

char1<-"你好"
char2<-'你好'
char1Vector<-c("你好","How are you?")

使用class()查詢上述物件類別。

若要產生小明說:"明天不用上課。",值的內容有包含",這時可改用成對的'定義字串值,如:

dialogue <- c('小明說:"明天不用上課。"',
               '小花說:"真的嗎?"')

dialogue # 顯示原始值的內容(含輸入時的引號)
cat(dialogue, sep="\n") # 顯示給人類看(含存出來)的值,sep="\n", 不同元素換行陳列。
writeLines(dialogue, "conversation.txt") # cat顯示的正是存出來樣貌。

在字串裡,\代表escape (from the original programming purpose):

  • n: original purpose是字母n

  • \n: 換行 (new line)

  • ": original purpose是用來定義字串的引號。

  • \": 單指平常我們說的引號。

context <- "...." # 請依下列情境輸入
cat(context)

請問context要怎麼寫才會使cat(content)出現下面的字眼

老師說換行要寫,

哪這句呢? 老師說換行要寫’’, ""是escape的意思

cat("老師說換行要寫\\n, \\是escape的意思")
cat("老師說換行要寫\'\\n\', \"\\\"是escape的意思")

有時一道指令太長為閱讀容易我們會想換行,但又不想讓R以為指令在第一行已經結束。R的換行原則是:

  • 只要指令定義不全,如括號不成對少右半邊(只有(沒看到))、結尾是, 等等

都表示指令不完整,R會再往下一行看,如下一行還是不完整就再往下一行。

以下三個指令都相同:

cat(dialogue, sep="\n")
cat(
  dialogue, sep="\n"
)
cat(
  dialogue,
  sep="\n"
)
  • 小心字串一對引號(“…”)中間不可隨便斷行,它會改變字串的內容。
"Today is a nice day."  #1
"Today is 
a nice day." #2
"Today is \na nice day." #3 same as #2

unicode: UTF-8/ UTF-16 https://www.branah.com/unicode-converter

"\u0928\u092e\u0938\u094d\u0924\u0947 \u0926\u0941\u0928\u093f\u092f\u093e"

查詢函數用法:

  1. Script window: 游標放在函數名稱任何一個位置,按F1。
  2. Console window: ?函數名稱 按enter。
  3. 右下角Help tab的放大鏡打函數名稱。
"<img src='https://ww"

3.4.3 Logical

邏輯:

  • 為「真」: TTRUE (要全大寫)。

  • 為「否」: FFALSE (要全大寫)。

logi1 <- c(T,TRUE,F,FALSE,FALSE)

邏輯值遇到數學運算時T會被當成1,而F會被當成0。

# 加總向量內的所有元素值。
sum(logi1)

3.4.4 typeof()

顯示atomic vector元素的基本認定型態,它代表電腦記憶體在儲存時真正看待的型態。

num <- c(1.5, 2, 3)
int <- c(1L, 3L, -2L)
char <- c("1.5","2","3")
logi <- c(T,T,F)

typeof(num)
typeof(int)
typeof(char)
typeof(logi)

3.4.5 class()

依資料的螢幕顯示型態及能對它進行的操作所做的分類。

class(num)
class(int)
class(char)
class(logi)

3.5 Atomic Vector Extended

以Atomic types為基礎,因應資料需求而延伸的資料儲存類別。以下介紹兩個數字串向量而延伸的兩個類別:

  • factor: 類別資料
factor(
  字串向量
)
  • POSIXct/POSIXt: 時間資料。
ymd_hms(
  字串向量
)

3.5.1 Factor

當資料值只有固定幾類反覆出現時,此類資料稱之為類別資料(factor or categorical data):

  • 系級
  • 性別
# 10位學生的主系
majors10_char <- c('經濟學系','社會學系','社會學系','經濟學系','經濟學系','社會學系','經濟學系','經濟學系','經濟學系','社會學系')

typeof(majors10_char)

[1] “character”

class(majors10_char)

[1] “character”

majors10_factor <- factor(majors10_char)
# 或
majors10_factor <- factor(
  c('經濟學系','社會學系','社會學系','經濟學系','經濟學系','社會學系','經濟學系','經濟學系','經濟學系','社會學系')
)

typeof(majors10_factor)

[1] “integer”

class(majors10_factor)

[1] “factor”

majors10_char及majors10_factor的螢幕顯示型態是不同的:

majors10_char
majors10_factor

as.integer()將資料的class轉成integer。由於factor被轉成integer後,其螢幕顯示會顯示電腦是用什麼數字在存這些類別資料。

as.integer(majors10_factor) 

數字與類別文字的對照表mapping table:

levels(majors10_factor)

Atomic vector延伸的類別,其基本儲存型態還是會落在先前的基本型態裡,但多了資料顯示的mapping table。

參考資料:https://r4ds.had.co.nz/factors.html

3.5.2 Class conversion

R有一系列的as.{class名稱}()的函數用來轉換物件的class;as.{type名稱}()用來轉換物件的type。

as.integer(...)會將…物件(嘗試)轉成integer class/type的物件。例如:

stringInteger <- c("1","2","-11")
class(stringInteger) # 無法進行數值運算
stringInteger+2
trueInteger <- as.integer(stringInteger)
class(trueInteger) # 可以進行數值運算
typeof(trueInteger)
trueInteger+2
char3 <- c("小明","1","3")
as.integer(char3)

R的函數均不會主動更改輸入物件,只會把結果值另行輸出,所以若要保留轉換結果則必需指定到一個物件名稱上。

as.integer(stringInteger)
class(stringInteger)
stringInteger <- as.integer(stringInteger)
class(stringInteger)

以下為學生學號

studentId <- c(410773002, 410773015)
  • 它目前是什麼class
  • 學號用什麼class比較合理

3.5.3 Date/Time

若收到兩筆訂單分別來自:

  • 台北 2020-03-18 13:52:40
  • 葡萄牙 Mar.18, 2020, 05:52:40
    上兩個時間/地點。哪一筆訂單最先進來?

答案是:同時間。

3.5.3.1 如何告訴電腦時間

如何比較兩時間的先後必需要將這兩個時間轉換成同時區。

處理時間所需資訊:

  • 時間輸入方式
  • 時間來自時區
  • 時間儲存以哪個時區為準(電腦進行比較用)

其中最後一點通訊協定是採UTC時區為公定標準,故不用煩惱。

library(lubridate)

台北

2020-03-18 13:52:40

  • 時間輸入方式: ymd_hms
  • 時間來自時區: Asia/Taipei
tpeTime <- ymd_hms("2020-03-18 13:52:40",
        tz="Asia/Taipei")

葡萄牙

Mar.18, 2020, 05:52:40

  • 時間輸入方式: mdy_hms
  • 時間來自時區: Europe/Lisbon
pgTime <- mdy_hms("Mar.18, 2020, 05:52:40",
                  tz="Europe/Lisbon")

來自相同時區相同輸入方式的時間文字字串「向量」,可以直接套入相同函數轉成Date/Time類別。

tpeTimeVector <- 
  ymd_hms(
    c("2020-03-18 13:52:40",
      "2020-03-11 03:12:40"),
    tz="Asia/Taipei"
    )
  • 若時間字串來自UTC,那可以不設定tz參數,即
ymd_hms(
c("2020-03-18 13:52:40","2020-03-11 03:12:40")
)
  • 若時間字串長得像“2020-03-11T06:56:17Z”,它來自UTC時區:
ymd_hms("2020-03-11T06:56:17Z")

3.5.3.2 type/class

typeof(tpeTime)
class(tpeTime)
## [1] "double"
## [1] "POSIXct" "POSIXt"
tpeTime
print(tpeTime)
cat(tpeTime)
## [1] "2020-03-18 13:52:40 CST"
## [1] "2020-03-18 13:52:40 CST"
## 1.585e+09

For DateTime class data, use as.character() before you print to ensure its human-readable.

cat(as.character(tpeTime))

2020-03-18 13:52:40

If you want to change how DateTime is shown in character, you use with_tz( ) to change the time zone setting.

範例:

tpeTime2 <- ymd_hms("2010-03-18 16:52:40",
        tz="Asia/Taipei")
pgTime2 <- mdy_hms("Mar.01, 2020, 03:52:40",
                  tz="Europe/Lisbon")
tpeTime3 <- 
  with_tz(tpeTime2, tzone="America/New_York")

print(tpeTime2)
print(tpeTime3)
## [1] "2010-03-18 16:52:40 CST"
## [1] "2010-03-18 04:52:40 EDT"
pgTime3 <- 
  with_tz(pgTime2, tzone="America/New_York")

print(pgTime2)
print(pgTime3)
## [1] "2020-03-01 03:52:40 WET"
## [1] "2020-02-29 22:52:40 EST"
  • DT: daylight time 日光節約時間
  • ST: standard time 標準(非日光節約時間)

with_tz()並不會改變電腦存DateTime的內容,電腦永遠是用UTC來看的秒數儲存。

as.numeric(tpeTime2)
as.numeric(tpeTime3)
## [1] 1.269e+09
## [1] 1.269e+09

因此,一旦時間正確告訴電腦儲存後要比較兩時間並不需要轉換成同時區:

pgTime2 - tpeTime2
pgTime3 - tpeTime3
## Time difference of 3636 days
## Time difference of 3636 days

List of Time Zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

3.5.3.3 how to pass DateTime around

When you pass datetime data to someone else, it is very likely the person does not use R. In this case, you should confine your data to only the basice THREE types (i.e. character, numeric, logical). It is better the date time passed out

  • as character.

  • follow some rule.

# Pass data as it is (Not good), not a character
tpeTime

# Method 1: convert to UTC and pass as character
tpeTime_utc <- with_tz(tpeTime, tzone="UTC")
as.character(tpeTime_utc)

The best way to pass date/time data is:

# Method 2: pass as a character that fits ISO8601 date time description standard with tz onset specified
tpeTime_iso8601 <- format_ISO8601(tpeTime, usetz=T)
tpeTime_iso8601

Every programming language understands ISO8601 with tzone onset. You don’t have to specify tz further. So:

tpeTime <- ymd_hms(tpeTime_iso8601)

3.6 List

3.6.1 Features

listObject <- list(value1, value2, value3)

特色:

  1. 每個元素值可以是不同type的物件值。

小明於「2020-03-31T13:40:55Z」在「一芳」買了「2杯」「水果茶」。

library(lubridate)

# Date/Time class
purchasingTime <- 
  ymd_hms("2020-03-31T13:40:55Z")

# character
location <- "一芳"
item <- "水果茶"

# numeric
quantity <- 2

mingsPurchase <-
  list(
    purchasingTime, 
    location, 
    quantity, 
    item
  )
print(mingsPurchase)
  1. 每個元素值可以取名。

每個元素以:

elementName = elementValue

形式輸入。elementName原則上要符合物件命名原則,若不合原則,可用反頓點。不過,也可以用字串形式輸入elementName。

mingsPurchase2 <-
  list(
    time = purchasingTime,
    'location' = location,
    "quantity" = quantity,
    `item name` = item
  )
print(mingsPurchase2)

Atomic vectors元素也可以命名。

heights = c(
  mary=162,
  john=177,
  steve=180
)
  1. 每個元素值也可以是vector形式(atomic vector 或 non-atomic vector, 如list, 均可以)。

這特色使得list比atomic vector多了使用層次結構來記錄資料關係的可能。

小明於「2020-03-31T13:40:55Z」在「一芳」買了「2杯」「水果茶」及「1杯」「日月紅茶」。

# 「**2杯**」「**水果茶**」
item1 <- list(
  quantity=2,
  `item name`="水果茶"
)
# 「**1杯**」「**日月紅茶**」
item2 <- list(
  quantity=1,
  `item name`="日月紅茶"
)

mingsPurchase3 <-
  list(
    time = purchasingTime,
    location = location,
    items=list(
      item1,
      item2
    )
  )

print(mingsPurchase3)

3.6.2 Common usage

Due to its flexibility, list (or its similar forms in other languages) is widely used as a container for raw data.

Example:

student1 <-
  list(
    name = character(),
    id = character(),
    gmail = character(),
    googleClassroom =
      list(
        displayName = character()
      ),
    github =
      list(
        username = character(),
        repoUrl = character()
      ),
    homeworks = list(),
    exams = list(),
    list()
  )

names(student1) # 每個元素名稱
length(student1) # 元素個數
str(student1, 1) # 第一層基本元素架構

one possible record:

hw1 <- list(
  date=ymd("2020-10-21"),
  grade=10
)
exam1 <- list(
  date=ymd("2020-10-28"),
  grade=50
)
student1 <-
  list(
    name = "Martin",
    id = "007",
    gmail = "mt@gm.ntpu.edu.tw",
    googleClassroom =
      list(
        displayName = "MT L"
      ),
    github =
      list(
        username = "tpemartin",
        repoUrl = "https://github.com/tpemartin/109-1-inclass-practice"
      ),
    homeworks = list(
      hw1
    ),
    exams = list(
      exam1
    ),
    list()
  )

str(student1, 2)

foodpanda 小聚餐酒館的資訊記錄形式。

  • 有分類:熱前菜、炸物、沙拉套餐…

  • 每一類裡附餐描述及很多品項

  • 每一品項有菜名、單價、原價

categoryTemplate = 
  list(
    categoryName=character(),
    items = list(
    )
  )

網路上的資料回傳大部份時候長得像list的格式,如之前查詢Google calendar使用者有多少個行事曆: https://developers.google.com/calendar/v3/reference/calendarList/list

這裡截取的是老師的行事曆中的“國立臺北大學 109 學年度行事曆”,同學如果到國立臺北大學google行事曆頁面去訂閱此行事曆再去查尋自己的行事曆有哪些,也會看到同樣一筆資料:

(省略前面)
 {
   "kind": "calendar#calendarListEntry",
   "etag": "\"1599191248726000\"",
   "id": "iu0vb797qntdh1odjcond1v24k@group.calendar.google.com",
   "summary": "國立臺北大學 109 學年度行事曆",
   "description": "此日曆是由 dawn@ntpu.org 擷取自國立臺北大學第 109 學年度行事曆製作。\n\n分享這個日曆:https://ntpu.org/109date\n回報日曆錯誤:https://cic.ntpu.org/dawn",
   "timeZone": "Asia/Taipei",
   "colorId": "18",
   "backgroundColor": "#b99aff",
   "foregroundColor": "#000000",
   "selected": true,
   "accessRole": "reader",
   "defaultReminders": [],
   "conferenceProperties": {
    "allowedConferenceSolutionTypes": [
     "hangoutsMeet"
    ]
   }
  }, 

這種回傳資訊格式叫做JSON(JavaScript Object Notation), 每個程式語法都會準備一個物件形式用來儲存接收到的JSON資料;R會用list type,Python會用dictionary type。

也因為每個程式都可接收JSON形式資料,不同程式開發者若有必要進行網路資訊交換時會將複雜資料結構轉成JSON再交換出去。

我們的程式要在網路送出commit資訊時,可以透過以下程序將list轉成JSON:

library(jsonlite) # 不少同學這行會有Error,還記得如何排除嗎?
toJSON(mingsPurchase3)

3.7 物件儲存

存下Global Environment中有的物件:

save(object1, object2, ..., file="myData.Rda")

下次取回來Global Environment用:

load("myData.Rda")
object1 <- c(2,5)
object2 <- ymd_hms(
  c("2015-03-22 12:28:42","2017-12-22 15:33:48"),
  tz="Asia/Taipei"
)
object3 <- list(2, FALSE, c("a","b","c"))
save(object1, object2, object3, file="threeObjects.Rda")
load("threeObjects.Rda")

3.8 綜合練習

  1. 執行以下程式,其中firstCommit及lastCommit均來自台灣時區:
browseURL("https://docs.google.com/spreadsheets/d/1EAG49qDKPWWi0ebkVr29TLrvVnoBPfkvxYY-J3xLAHY/edit#gid=458686645")

選一位學生將他的created_at, firstCommit, lastCommit存成Date/Time(即POSIXct) class的向量。