プログラミングの基本

定義、順次

変数

  • 値をメモリに一時的に保存するための入れ物(と考える)
    • 定義(変数を作る)、代入(変数に値を入れる)、削除(変数の消去)が可能
  • 変数にはがある
    • 異なる型同士を結合させることはできない!
    • R の場合は、変数を定義する際に型を指定する必要はない
  • R の型の代表例を以下に示す

文字列型 (character)

character <- "あいうえお"
character |> class()
[1] "character"
  • 数値も文字列型で示すことができる
    • ただし、文字列型の場合は計算には使えない!
    • 分析で使用したいなら、(グループ分けのために使う場合を除いては)数値は数値型に変換する必要
character2 <- "12345"
character2 |> class()
[1] "character"
character2 |> as.numeric() # 数値型への変換
[1] 12345

数値型 (numeric)

numeric <- 12345
numeric |> class()
[1] "numeric"

論理値型 (logical)

  • TRUE or FALSE (全て大文字!)
logical <- TRUE
logical |> class()
[1] "logical"

日付型 (Date)

date <- lubridate::date("2020/04/01")
date |> class()
[1] "Date"

データ構造

  • 変数を格納する変数(と考える)
  • Rにおけるデータ構造の代表例を以下に示す

ベクトル

  • 値は全て同じ型である必要
    • 数値と文字列を混入したら、文字列型に自動で変換される
vector <- c(1, 2, 3)
vector |> class()
[1] "numeric"
sum(vector)
[1] 6
vector * 2
[1] 2 4 6

行列 (matrix)

matrix <- matrix(c(1, 2, 3, 4, 5, 6), ncol = 2, byrow = TRUE)
matrix |> class()
[1] "matrix" "array" 
matrix
     [,1] [,2]
[1,]    1    2
[2,]    3    4
[3,]    5    6

リスト (list)

  • 異なる型を混在できる
list <- list(c(1, 2, 3), 4, "5", "A", TRUE)
list |> class()
[1] "list"
list
[[1]]
[1] 1 2 3

[[2]]
[1] 4

[[3]]
[1] "5"

[[4]]
[1] "A"

[[5]]
[1] TRUE

表 (table)

  • データの個数の計測に便利
table <- runif(100) |> # 一様分布 (0-1の間の値から一様の確率で乱数を100個作成)
  round() |> # 四捨五入し整数に
  table() # table にする
table |> class()
[1] "table"
table

 0  1 
50 50 

データフレーム (data.frame)

  • データフレームの基本構造
    • 列ごとに型を統一する必要
    • 後述するtibbleも、裏ではdata.frameを使っている (data.frameの上位互換)
data_frame <- data.frame(
  ID = c(1, 2),
  pref = c("東京都", "神奈川県"),
  city = c("港区", "横浜市")
)
data_frame |> class()
[1] "data.frame"
data_frame

関数

  • 入力された値 (=引数<ひきすう>) をもとに、一括りになった処理を実行し、値 (=返り値) を出力するためのもの
    • 同じようなコードを何度も書く必要がなくなる!

関数の定義

  • 東京に住む場合の理想家賃を額面収入から推定する関数を定義してみる
    • この例での引数は、最初の行の()内で示した annual(=年収)とmonthly (=月収)
      • これらの初期値はNULL (=無) とした
        • 該当する引数が指定されずに実行された場合は、初期値が採用される
calc_rent <- \(annual = NULL, monthly = NULL){
  if(!is.null(monthly)){
    annual <- monthly * 12
  }
    rent_monthly_max <- 4.796e-01 + 9.784e-11 * (annual ^ 3) - 1.400e-06 * (annual ^ 2) +
2.036e-02 * annual     

 rent_monthly_ideal <- 3.997e-01 + 8.153e-11 * (annual ^ 3) - 1.167e-06 * (annual ^ 2) +
1.697e-02 * annual 
 
 print(
   paste0("負担_理想 (.25): ", round(rent_monthly_ideal, digits = 1), "万円")
 )
 
 print(
   paste0(
     "負担_最大 (.3): ", round(rent_monthly_max, digits = 1), "万円")
   )
  }

関数の実行

calc_rent(monthly = 30)
[1] "負担_理想 (.25): 6.4万円"
[1] "負担_最大 (.3): 7.6万円"
calc_rent(annual = 500)
[1] "負担_理想 (.25): 8.6万円"
[1] "負担_最大 (.3): 10.3万円"

スコープ

  • 変数への代入を関数の外で行った場合と、関数の中で行った場合とでは、挙動が変わることがある
    • 関数の中で変数を代入した場合は、関数の中でしか保持されない「ローカル変数」が作成され、その中に代入される
      • 関数の外で定義した「グローバル変数」には反映されない!
        • assign関数を用いれば、直接グローバル変数に代入することが可能
variable <- 100  ## グローバル変数
print(variable)
[1] 100
(\(){
  variable <- 200  ## ローカル変数
  print(variable)
})()
[1] 200
print(variable) ## グローバル変数 (ローカル変数は反映されない!)
[1] 100
variable <- 100  ## グローバル変数
print(variable)
[1] 100
(\(){
  variable <- 200  ## ローカル変数
  print(variable)
  assign("variable", variable, envir = globalenv()) ## ローカル変数の中身をグローバル変数に割り当てる
})()
[1] 200
print(variable) ## グローバル変数
[1] 200

反復

  • 同じ処理を何度も繰り返す場合は、for文やwhile文などの反復構文を用いると便利
    • ループを終了させるための条件を必ず指定
      • でないと無限ループになる!

for

for(i in 1:10){
    print(paste0(i, "秒 経過"))
    Sys.sleep(1)
}
for(time_for in 10:0){
    print(paste0("残り ", time_for, "秒"))
    Sys.sleep(1)
}

while

time_while <- 10
while(time_while >= 0){
    print(paste0("残り ", time_while, "秒"))
    time_while <- time_while - 1
    Sys.sleep(1)
}

再帰

  • 関数の中でその関数自身を呼び出すことも可能
wait_time <- \(time){
  if(time > 0){
    print(paste0("残り ", time, "秒"))
    Sys.sleep(1)
    wait_time(time - 1)
  }
  else{
    print("お待たせしました!")
  }
}
wait_time(10)

条件、分岐

  • 条件分岐は、if文や、dplyrパッケージのif_else、case_match、case_whenの各関数が便利
    • ifelseやswitchなど、デフォルトの関数には致命的なデメリットがあるものも存在することに注意!

if

day <- lubridate::today() |>
  lubridate::day()

if(day %% 2 == 0) {
  print("偶数の日")
} else {
  print("奇数の日")
}
[1] "奇数の日"
day <- lubridate::today() |>
  lubridate::day()

if(day %% 7 == 0) {
  print("0")
} else if (day %% 7 == 1) {
  print("1")
} else if (day %% 7 == 2) {
  print("2")
} else if (day %% 7 == 3) {
  print("3")
} else if (day %% 7 == 4) {
  print("4")
} else if (day %% 7 == 5) {
  print("5")
} else {
  print("6")
}
[1] "3"
day <- lubridate::today() |>
  lubridate::day()

if(day %% 2 == 0) {
  print("偶数")
  if (day %% 3 == 0) {
    print("6の倍数")
    if (day %% 4 == 0) {
      print("12の倍数")
    }
  }
}

ifelse

  • デフォルトのifelse関数は、中で使用した変数の型の情報を消してしまうので使わない!
    • 代わりに、dplyr::if_else関数を使おう
day <- lubridate::today() |>
  lubridate::day()

## 今日の日が偶数なら今日の日付を、奇数なら翌日の日付を出力

ifelse(day %% 2 == 0, as.Date(lubridate::today()), lubridate::today() + 1)
[1] 19556

dplyr::if_else

day <- lubridate::today() |>
  lubridate::day()

## 今日の日が偶数なら今日の日付を、奇数なら翌日の日付を出力

dplyr::if_else(day %% 2 == 0, as.Date(lubridate::today()), lubridate::today() + 1)
[1] "2023-07-18"

switch

  • 基準となる変数を文字列にしなければならないのが欠点
    • ゆえに、switchではなく、dplyr::case_matchを使おう
day <- lubridate::today() |>
  lubridate::day()

day <- day %% 2 |> as.character()

switch (
  day,
  "0" = "偶数の日",
  "1" = "奇数の日"
)
[1] "奇数の日"

dplyr::case_match

day <- lubridate::today() |>
  lubridate::day()

day <- day %% 2

dplyr::case_match(
  day,
  0 ~ "偶数の日",
  1 ~ "奇数の日"
)
[1] "奇数の日"

演算子

算術演算子

構文 意味
a + b aにbを足した結果の値
a - b aからbを引いた結果の値
a * b aにbを掛けた結果の値
a / b aをbで割った結果の値
a %/% b aをbで割った際の商(整数)
a %% b aをbで割った際の余り(整数)
a ^ b aのb乗の値

関係演算子

構文 意味
a > b aはbよりも大きいか?
a >= b aはb以上か?
a < b aはbよりも小さいか?
a <= b aはb以下か?
a == b aはbと等しいか? (文字列でも利用可)
a != b aはbと等しくないか? (文字列でも利用可)

論理演算子

構文 意味
a & b aかつb
a | b aまたはb
!a aではない

R 特有の演算子

構文 意味
a %in% c(a, b, c) aはc(a, b, c)のグループに含まれるか?
a %*% b aとbの行列の積
a %o% b aとbの外積

パイプ演算子

  • パイプライン (パイプ演算子とも。R では |> ) の左辺が、右辺の第1引数になる

    • 中間に挟むオブジェクトを作成しなくて良くなる
    • コードの可読性が向上する
        result <- lm(income ~ age, data = data) # resultが中間のオブジェクト
        summary(result)
  • ↑ is equal to ↓

    lm(income ~ age, data = data) |> # resultが不要に!
    summary() # 第1引数はパイプラインの前の関数
  • “|>” は Ctrl (or Command) +Shift+M で入力できるように設定できる

    • デフォルトでは %>% と入力される。%>% は tidyverse に依存するもの(下記の “magrittr のパイプライン” を参照)
    • |> は バージョン 4.1 以降の R でのみ使用可能なことに注意
  • “Tools” -> “Global Options” -> “Code” -> “Use native pipe operator, |>” にチェックして”OK”

【参考】magrittr の パイプライン

  • バージョン 4.1 以前 の R では,パイプラインを使いたい場合,tidyverseパッケージ群の一つである magrittr パッケージを用いるのが主流だった

  • バージョン 4.1 の R から、パイプ演算子 |> が標準で導入された

    • magrittr より高速 
  • magrittr でのパイプ演算子: %>% (%で括る)

      data %>%
        select(row1, row2) %>%
        lm() %>% summary()
    • 第2引数以降に左辺を渡したい場合: 該当引数にドットを挿入
          data %>%
            select(row1) %>%
            mutate(rowNew = as.numeric(.)) # .の部分に左辺が挿入される
    • バージョン 4.2 以降の R の |> パイプライン では、ドットの代わりにアンダーバーを引数として使える
      • ただし、使えるのは一か所のみ
  • %$% 演算子 : 左辺内の変数にアクセス (= “<左辺>$”が実行時に補われる)

data %$% lm(row1, row2) %>% summary()
  • %<>% パイプ演算子 : 左辺を右辺の第1引数に & 右辺を左辺に格納
data <- data %>% select(row1, row2)
data %<>% select(row1, row2) # 上行と同じ意味
  • %T>% 演算子 : 右辺の返り値は無視 & 右辺も実行される
data %>%
  select(row1, row2) %T>%
  plot() %>% # plotが無事に出力される
  lm() %>% summary() # 無事にsummaryも出力される