20151023 [Coursera] R Programming (9)

整理自 R Programming (Week 2) -- Scoping Rules

[ Week 2 課程內容 ]
 
 
[ 筆記內容 ] 作用域規則
  • (一)Scoping Rules - Symbol Binding
  • 1.  符號的順序
  • 2.  作用域規則
  • 3.  詞法作用域 (lexical scoping)
  • (二)Scoping Rules - R Scoping Rules
  • 1.  詞法作用域 (lexical scoping)
  • 2.  探索"函數閉包 (Function Closure)"
  • 3.  "詞法作用域 (Lexical Scoping)" VS "動態作用域 (Dynamic Scoping)"
  • 4.  "詞法作用域 (lexical scoping)"的使用結論
  • (三)Scoping Rules - Optimization Example (OPTIONAL)
  • 1.  優化 (Optimization)
  • 2.  結論 >> 關於"詞法作用域 (Lexical Scoping)"
 
 
 
 
[ 參考資料 ]
 
 
[ 重點整理 ] 
1.  符號的順序
  • [ 情況 ] 
  • 當你在命令列定義"lm()",在使用"lm()"時, R 會使用你定義的"lm()" ? 還是使用 R 套件包裡面的"lm()" ?
# 原先 R 裡面的"lm()"是用來擬合線性模型。
> lm <- function(x) { x * x }    # 重新定義"lm()"
> lm    
function(x) { x * x }
 
  • 符號的搜尋順序 << 當 R 收到賦值需求時
  1. 搜尋全域環境中是否有相同名稱的符號。
  1. 搜尋列表中每一個包的命名空間裡是否有相同名稱的符號 (包括當前載入的 R 套件包)。
# 使用"search()"顯示 R 搜尋列表的內容(依照順序排列)。
> search()
 [1] ".GlobalEnv"        "tools:rstudio"    
 [3] "package:stats"     "package:graphics" 
 [5] "package:grDevices" "package:utils"    
 [7] "package:datasets"  "package:methods"  
 [9] "Autoloads"         "package:base"     
  • [ 結論 ]
  • 各個套件包在搜尋列表上的排列順序十分重要!
  • 在一般情況下"全域環境(global environment)"是搜尋列表中的第一個元素;而 "base套件包(base packages)"最後一個元素
  • 用戶可以隨時指定要加載哪些套件包,也就是說搜尋列表的順序並不是固定的;不同時刻,搜尋列表可以有不同的順序
  • 當使用者用"library()"指令加載某套件包時,該套件的命名空間會被排列在第二位(也就是全域環境後面),其他的套件包的順序都會順勢往後移一位。
  • "函數"和"非函數"在 R 裡有不同的命名空間也就是說名為"c"的物件和名為"c"的函數可以同時存在。
 
 
2.  作用域規則
  • [ 情況 ]
  • 一個值如何與函數中的自由變數建立關聯? << 使用"作用域規則"
  • 函數中有兩種變數 : 
  • 形式參數 >> 透過函數的定義傳入函數內
  • 局部變數或自由變數 (非參數) >> 存在於函數中
  • [ 種類 ]
  • "動態作用域 (dynamic scoping)"
  • "詞法作用域 (lexical scoping)" (或者稱為 "靜態作用域 (static scoping)" )
  • [ 優點 ] 能夠簡化運算,適用於統計運算。
  • [ 相關 ]
  • 如何使用搜尋列表給符號賦值?
 
 
3.  詞法作用域 (lexical scoping)
  • 在定義的函數環境中搜尋自由變數的值
  • [ 情況 ] 如何給自由變數賦值?
  • 形式參數 : x、y
  • 自由變數 : z
# 這裡有一個自由變數: z (未在函數中定義)
f <- function(x, y) {
 x^2 + y / z
}
  • [ 基本認知 ] 什麼是環境?
  • 環境是"符號"和"值"(一對)的集合 (每個符號都有與之對應的值)
  • [ 提示 ] 把工作空間想像成是一系列的符號和值對
  • 通常每個環境都有一個"父環境 (parent environment)";而對一個環境而言,可能有多個以上的"子環境 (children environment)"
  • [ 特例 ] 只有"空環境 (empty environment)"沒有"父環境 (parent environment)"
  • "函數 (function)" + "環境 (environment)" =  "閉包 (closure)" 或稱為 "函數閉包 (function closure)"
 
  • 尋找"自由變數 (free variable)" << 一層一層往上找
  1. 如果某個自由變數的值無法在函數中找到,就會在該函數的父環境中繼續尋找 (如果找不到,就在父環境的父環境中尋找...以此類推)
  1. 如果函數是在全域環境中被定義,而函數中自由變數的值無法在函數中和全域環境中找到,就會尋找全域變數的父環境(也就是搜索列表中的下一個元素...以此類推,直到找到自由變數的值為止)
  • 如果自由變數在全域變數和其他搜尋列表的元素中都找不到對應的值,就會到達"空環境 (empty environment)",倘若無法在上述環境找到對應的值,就會出現錯誤。
 
 
 
 
[ 參考資料 ]
 
 
[ 重點整理 ]
1.  詞法作用域 (lexical scoping)
  • 為什麼很重要?
  • 通常只要在全域環境內定義函數,就能在用戶的工作區裡找到自由變數的值。
  • 在 R 裡面,你可以在函數內定義另外一個函數。
  • [ 例如 ] 在 A 函數內定義 B 函數
  • 在一個函數的回傳值可以是一個函數 (也可以是列表、向量、矩陣、資料框 ...)。
  • 對於在 A 函數內被定義的 B 函數而言,其父環境就是 A 函數。
  • [ 範例 ] 在函數內定義另外一個函數
# 在函數內定義另外一個函數
> make.power <- function(n) {  # "make.power()"的參數"n"
+     pow <- function(x) {     # "pow()"的參數是"x"
+         x^n
+     }
+     pow                      # 回傳"pow()"的值
+ }
>
> cube <- make.power(3)
> square <- make.power(2)
> cube(3)
[1] 27
> square(3)
[1] 9
  1. 對於"pow()"來說"n"是自由變數,因為"n"並不是在"pow()"裡面定義的
  1. "n"是"make.power()"裡面的變數;同時"make.power()"也是定義"pow()"的環境 (相當於"pow()"的父環境)。
  1. 綜合上述兩點,"pow()"可以在"make.power()"這個環境中找到"n"的值
 
 
2.  探索"函數閉包 (Function Closure)"
  • 如何得知一個函數所在的環境中有什麼東西? << 使用"ls()"
# 使用"ls()"查看環境
> ls(environment(cube))
[1] "n"   "pow"
> get("n", environment(cube))
[1] 3
> ls(environment(square))
[1] "n"   "pow"
> get("n", environment(square))
[1] 2 
 
 
3.  "詞法作用域 (Lexical Scoping)" VS "動態作用域 (Dynamic Scoping)"
  • 除了 R 以外,支援"詞法作用域 (Lexical Scoping)"的程式語言 : 
  • Scheme
  • Perl
  • Python
  • Common Lisp
 
  • [ 範例 1 ] 詞法作用域
# "詞法作用域 (Lexical Scoping)"
> y <- 10               # "y"
> f <- function(x) {    # "f()"
+     y <- 2
+     y^2 + g(x)    # 這裡"y"的值為"2"
+ }
> g <- function(x) {    # "g()"
+     x*y           # 這裡"y"的值為"10"
+ }
> f(3)
[1] 34
  • 在"f()"中,"y"和"g()"都是自由變數。
  • 在"g()"中,"y"是自由變數。
 
  • [ 差異比較 ] 
  • 詞法作用域 ( Lexical Scoping )  << 上述範例中使用
  • 在詞法作用域下"g()"中"y"的值,是在定義這個函數的環境中找到的 (在這裡是全域環境)所以函數"g()"中"y"的值就是"10"
  • 動態作用域 ( Dynamic Scoping )
  • 在動態作用域下"g()"中"y"的值,需要考慮呼叫這個函數的環境 ( 呼叫環境, calling environment );在 R 中的 calling environment 就是"父框架 ( parent frame )",對於"g()"而言"f()"就是它的父框架,所以函數"g()"中"y"的值就是"2"
 
  • [ 範例 2 ] 特殊情況 - 在全域環境中定義函數
# "詞法作用域"VS"動態作用域"的結果看起來相同
> # 情況1:還沒有定義"y"
> g <- function(x) {    # "x"是形式參數
+ a <- 3
+ x+a+y    # "y"是自由變數
+ }
> g(2)
Error in g(2) : object "y" not found
>
> # 情況2:已經定義"y"了
> y <- 3
> g(2)
[1] 8
 
 
4.  "詞法作用域 (lexical scoping)"的使用結論
  • 所有的物件都必須要保存在記憶體內
  • 所有函數都一定有一個指標來指向它的定義環境 (定義環境可以是任何地方,因為函數裡可以再定義另一個函數)
 
 
 
 
[ 參考資料 ]
 
 
[ 重點整理 ] 作用域規則的應用
  • 為什麼作用域規則很重要?
1.  優化 (Optimization)
  • [ 基本思想 ]
  • 把目標函數所依賴的所有數據以及其他東西,都包含在目標函數的定義環境中。
  • 在 R 裡的優化函數像是 : optim()、nim()、optimize(),它們都要求輸入一個目標函數 (目標函數的參數是向量),當優化工作進行時,使用者須固定參數的值。
  • 目標函數除了參數之外,往往還和其他物件有關 (如 : 數據)
 
  • [ 範例 ] 對數概似函數
  • optim()、nim()、optimize() : 預設為"取得函數的最小值",如果想求最大值則應該取負值後再進行最小化。
  • "make.NegLogLik()" 的參數 : 
  • 第一個參數 : data (數據)
  • 第二個參數 : fixed (邏輯向量) >> 決定是否要固定目標函數的某些參數
  • 正態分布的兩個參數 : mu (均值) sigma(標準差)
# 對數概似函數
> make.NegLogLik <- function(data, fixed=c(FALSE,FALSE)) {
+     params <- fixed
+     function(p) {
+         params[!fixed] <- p
+         mu <- params[1]
+         sigma <- params[2]
+         a <- -0.5*length(data)*log(2*pi*sigma^2)
+         b <- -0.5*sum((data-mu)^2) / (sigma^2)
+         -(a + b)
+     }
+ }
>
>
> set.seed(1); normals <- rnorm(100, 1, 2)
> nLL <- make.NegLogLik(normals)
> nLL
function(p) {
        params[!fixed] <- p
        mu <- params[1]
        sigma <- params[2]
        a <- -0.5*length(data)*log(2*pi*sigma^2)
        b <- -0.5*sum((data-mu)^2) / (sigma^2)
        -(a + b)
    }
<environment: 0x08d25c7c>
> ls(environment(nLL))
[1] "data"   "fixed"  "params"
>
>
> optim(c(mu = 0, sigma = 1), nLL)$par
      mu    sigma 
1.218239 1.787343
>
> nLL <- make.NegLogLik(normals, c(FALSE, 2))
> optimize(nLL, c(-1, 3))$minimum
[1] 1.217775
> nLL <- make.NegLogLik(normals, c(1, FALSE))
> optimize(nLL, c(1e-6, 10))$minimum
[1] 1.800596
>
> nLL <- make.NegLogLik(normals, c(1, FALSE))
> x <- seq(1.7, 1.9, len = 100)
> y <- sapply(x, nLL)
> plot(x, exp(-(y - min(y))), type = "l")
>
>
> nLL <- make.NegLogLik(normals, c(FALSE, 2))
> x <- seq(0.5, 1.5, len = 100)
> y <- sapply(x, nLL)
> plot(x, exp(-(y - min(y))), type = "l")
>
 
 
2.  結論 >> 關於"詞法作用域 (Lexical Scoping)"
  • 如果要進行某種優化,能夠建立包含所有必備數據或物件的目標函數
  • 在每一次估計這個函數的時候,不必加上很長的參數列表。
  • 程式的設計可以非常簡潔明瞭。