2015/10/26

split-apply-combine strategy in R: R 的分進合擊

split-apply-combine SAC strategy 是 R 語言在處理大量資料的策略,有人直接翻譯成「拆開-套用-整合」,有人翻譯成「化整為零」,我有另一個更貼切的翻譯:「分進合擊」。

SAC strategy 是用來處理大數據的策略,當某一個類型原始資料的數量很多,有數萬、數十萬以上的資料筆數的時候,可以先將資料切割分塊 (split),然後套用 (apply) 運算在分塊的數據資料上,最後再合併 (combine) 運算結果,將結果全部一次回傳給使用者。

  1. "split" the original dataset
  2. "apply" the computation to each dataset
  3. "combine" the result into a new single dataset

聽起來跟先前比較熟悉的 Map-Reduce 很像,差別只在於 Map-Reduce 是用在大量機器的分工處理上,split-apply-combine 是用在單機的資料分析處理。

如果有用過 Mathemetica 這種數學軟體的人,就會知道先將資料存放到矩陣當中,再利用平行計算的方式,可以很快地就得到每一行或是每一列的總和。

一般用途的程式語言,要實作一個 excel 或 csv 二維矩陣資料的運算時,通常會直覺地以迴圈進行運算,然而這些迴圈在經過 compiler 編譯後得到的機器碼,我們回看到機器是循序地一個一個地取出資料,計算後儲存到暫存變數,然後再取出下一個資料進行運算,總終就能得到結果,計算第二行的總和只能等到第一行加總處理完成後,才會進行計算。

但最好的方式,是同時針對每一行的資料,同時做加總,最後同時得到結果,也就是將整個矩陣,split 成一行一行的單位,接著以行為單位,apply 加總運算,最終 combine 每一行的總和到新的 dataset。

雖然 R 語言也有支援一般語言的迴圈語法,但最重要的是,R 語言的 apply 相關函數。

以下是最基本,使用 apply 處理矩陣的行/列總和的範例:

# 產生 3x3 矩陣
> theMatrix <- matrix(1:9, nrow = 3)

# 每一橫排的總和
> apply(theMatrix, 1, sum)
[1] 12 15 18

# 每一直排的總和
> apply(theMatrix, 2, sum)
[1]  6 15 24

其他比較常用的是 lapply 與 sapply,跟 apply 的差別是 lapply, sapply 是用來處理 list。

# 產生 list,兩個元素:3x3 矩陣與向量
> theList <- list(A = matrix(1:9, nrow=3), B=1:5)
# 計算總和,並以 list 為回傳值
> lapply(theList, sum)
$A
[1] 45

$B
[1] 15

# 計算總和,以 vector 為回傳值
> sapply(theList, sum)
 A  B 
45 15

mapply 是將某個函數,同時套用在多個 list

# 產生 3 個 list
> list1 <- list( A = matrix(1:9, nrow=3), B = matrix(1:16,nrow=2), C=1:5)
> list2 <- list( A = matrix(1:9, nrow=3), B = matrix(1:16,nrow=8), C=15:1)
> list3 <- list( A = matrix(1:9, nrow=3), B = 15:1, C = 1:10 )

# 同時套用 sum
> mapply( sum, list1, list2, list3 )
  A   B   C 
135 392 190 

# 同時套用 identical 
> mapply( identical, list1, list2, list3 )
    A     B     C 
 TRUE FALSE FALSE

現在原生的 apply 相關函數,已經被 Hadley Wickham 提供的 plyr 套件取代了,Split-Apply-Combine2 提供了一個整理好的 table。

- array data frame list nothing
array aaply adaply alply a_ply
data frame daply ddply dlply d_ply
list laply ldply llply l_ply
n replicates raply rdply rlply r_ply
function arguments maply mdply mlply m_ply

所有的函數都是以 *ply 為結尾,前面兩個字母分別代表著資料結構,例如 ddply 就是輸入 data.frame 資料,運算後,取回 data.frame 資料,dlply 是輸入 data.frame,運算後,取回 list 資料,第二個字元如果是底線 _ ,代表沒有輸出的資料。

plyr 裡面包含了一份 1871 ~ 2007 年 1228 個 baseball batting 的資料,裡面只包含了超過 15 個球季的 MLB 球員資料,總共有 21,699 個 records。

> require(plyr)
> head(baseball)
           id year stint team lg  g  ab  r  h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
4   ansonca01 1871     1  RC1    25 120 29 39  11   3  0  16  6  2  2  1  NA   0 NA  0   NA
44  forceda01 1871     1  WS3    32 162 45 45   9   4  0  29  8  0  4  0  NA   0 NA  0   NA
68  mathebo01 1871     1  FW1    19  89 15 24   3   1  0  10  2  1  2  0  NA   0 NA  0   NA
99  startjo01 1871     1  NY2    33 161 35 58   5   1  1  34  4  2  3  0  NA   0 NA  0   NA
102 suttoez01 1871     1  CL1    29 128 35 45   3   7  3  23  3  1  1  0  NA   0 NA  0   NA
106 whitede01 1871     1  CL1    29 146 40 47   6   5  1  21  2  2  4  1  NA   0 NA  0   NA

以下的範例,利用 ddply 對 baseball 資料進行統計,我們可以算出全壘打數量最多的選手,是 Barry Bonds。

# 製作加總所有全壘打數量的函數
> calhr <- function(data) { c(TOTALHR = with(data, sum(hr))) }

# 利用 ddply,對每一個球員,進行 calhr 運算,計算結果會放到 TOTALHR
> totalhr <- ddply(baseball, .variable="id", .fun=calhr )

# 針對 TOTALHR 進行排序
> totalhr <- totalhr[ order(totalhr$TOTALHR, decreasing=TRUE), ]

# 列印生涯全壘打數量前十名的選手
> head(totalhr, 10)
            id TOTALHR
95   bondsba01     762
1    aaronha01     755
964   ruthba01     714
707   mayswi01     660
1045  sosasa01     609
424  griffke02     593
946  robinfr02     586
726  mcgwima01     583
590  killeha01     573
849  palmera01     569

2015/10/19

軟體需求層次理論

在心理學領域中,馬斯洛的需求層次理論將每一個人心理面的需求,分為五個等級層次,而在教育界,會運用需求層次理論分析孩子的心理需求,輔導孩子將需求動機的成長至高層次的自我實現,這種方式屬於人本主義心理學。

軟體需求跟個人的需求層次有些類似,通常會希望軟體這樣輔助的工具,能在滿足基本的功能需求之外,附帶著非功能的需求,這些非基本功能的需求,也可以說是軟體本身帶給使用者的一種文化,或者可以說是一個使用這個軟體才能得到的一種滿足感。

需求層次理論 Maslow's hierarchy of needs

美國心理學家亞伯拉罕‧馬斯洛於1943年在《人類激勵理論》論文中提出需求層次理論,該理論將需求分為五種,也就是五個層次,分別為:生理,安全,情感和歸屬,尊重,自我實現。另外兩種需求:認知和審美,沒有列入需求層次排列中,他認為這二者應居於尊重與自我實現需求之間。

生理、安全、情感和歸屬、尊重,這四種屬為基本需求,認知、審美和自我實現,這三種歸為發展需求。基本需要滿足之後,自我實現的獨特天賦需求,就成為我們的最高目標。

需求層次理論

馬斯洛需求層次理論

馬斯洛需求層次理論

心理學第三勢力:人本主義心理學派(Humanistic Psychology))

軟體需求層次理論

軟件需求層次理論

The Hierarchy of Needs

上面這篇文章,對需求層次理稐的理解套用在使用者對軟體的需求管理上,軟體面對著一群使用者,因應使用者的需求,產生了軟體系統,但是軟體系統通常是交給軟體開發公司進行開發,而軟體工程師常常不是這個軟體的使用者。

軟體公司需要一個需求的管理方式,分析使用者提出的軟體需求,不管透過什麼篩選的方式,最終要得到的,就是需求的重要性以及開發單位進行需求開發的優先等級。

這篇文章提出了一個分析方式:NFC,也就是「需求(Need)」、「特性(Feature)」、「能力(Capacity)」,需求以及特型分別為縱與橫軸,需求項目則填寫在這個功能矩陣當中,也就是將需求項目以馬斯洛需求層次理論進行分類。

產品經理的工作是根據馬斯洛的需求層次理論,考慮待辦事項列表中的特性如何滿足用戶的需求。在更低級的需求充分滿足的情況下,總是為滿足最高級需求的特性賦予最高的優先級。

最基本的功能必須先滿足,中間層次的需求則有轉圜的空間,而最上層的需求,會成為使用者認定,一定要用你的軟體的特殊理由。

Kano Model

Kano Model 是狩野教授在1984年發表的滿意度模型。在 Kano Model 發表之前,滿意度評估衡量只使用 「滿意 - 不滿意」 一個維度。

Kano Model 運用赫茲伯格雙因子理論的概念,增加 「滿意 - 沒有滿意」 、 「不滿意 - 沒有不滿意」 兩個維度作為滿意度指標的分類方式,這兩個維度在雙因子理論裡叫做激勵因子與保健因子。

增加使用兩個維度去衡量客戶滿意度會比較貼近客戶真實的狀況,因為有些產品功能對客戶來說,客戶會認為某項功能本來就應該要有,例如 Office Word 的」檔案儲存」功能,有這項功能並不會讓客戶滿意,但沒有這項」檔案儲存」功能,就會造成嚴重的不滿意。

有些功能會超乎客戶期待,沒有時不會造成不滿意,有的話則會提高滿意度。只使用一維的 「滿意 - 不滿意」 的評量方式無法辨認出以上的狀況,所以需要 Kano Model 做進一步的分析。

Kano Model 滿意度模型

這篇文章,提供了完整的範例,由使用者的問卷調查的設計開始,必須要有正面以及反面的問題,將問題的答案以A、O、M、I、R、Q 六種屬性歸類。

分析出每項功能的屬性後,就可以用來做排序改善的優先順序,以重要程度來說,M (Must-be) > O (One-dimensional) > A ( Attractive) > I (Indifferent),優先改善 Must-be 屬性的功能,先降低不滿意度,然後在做提高滿意度的改善。

KANO模型簡介

狩野紀昭 (Noriaki Kano) 二維品質模式

Kano Model

討論

如果可以直接面對大量使用者,並能讓使用者填寫問卷調查,由問卷調查的結果,可以套用 Kano Model 進行需求分析,得到需求項目的優先等級順序。

但通常沒有辦法要求使用者填寫太多問題的問卷,因為使用者不會花時間在五個問題以上的畫面,頂多只能一到兩個問題,太多問題就會選擇直接跳出問卷功能。這時候,可以使用承襲自馬斯洛的需求層次理論的軟體需求層次理論 NFC,先進行初步的功能分級。

在軟體需求層次理論 NFC 的結果中,得到的數個高優先權的需求項目之後,就可以考慮以一個問題詢問使用者,進行問卷調查,藉此得到最高優先權的需求項目。

2015/10/12

搞什麼,軟體架構師只要會畫圖嗎?

真的嗎?當一個軟體架構師,只要會畫畫方塊圖,把軟體元件用線連一連就好了嗎?如果有個人說他是在做架構師的工作,那麼他平常都在做什麼呢?Architect 在軟體公司,是個令人稱羨的工作,他在團隊中,代表有著累積出來的工作經驗,也有絕對的說話份量,但這一切都只是畫畫元件關係圖,就能夠表現出來的嗎?

軟體架構師在幹麻?

你是個軟件架構師嗎? 這篇文章對軟體架構提出了一些看法。

軟體架構可區分為五個部份:

  1. 管理非功能性需求
    設計架構必須要先知道系統的規模,使用的方式,使用的對象等等非功能性需求,才能根據這些需求,提出一個「適當」的軟體架構。

  2. 定義元件架構
    邏輯分割系統的功能模組,適當地區分這些模組的用途以及互通性,並在後面決定實作的方式以及負責的人選。

  3. 選擇實作的技術
    根據團隊成員的技術能力背景以及狀態,決定實作的技術,不同的專案時程,會有不同的忍受度,也間接決定,要讓哪些成員使用那一種技術實作,如果是短期要收到成果的專案,不用考慮太多,就直接選擇最熟悉的技術以及最熟悉的專案成員去實作。

  4. 評估架構
    架構中如果有一些技術瓶頸,需要預先測試用以判斷技術的可行性,這時候需要先做一些 prototype。要確認架構是否可行,必須搭配一些前期測試,先將專案的 critical path 實作出來,並以前期測試的方式,評估架構的可行性。

  5. 架構協作
    當專案成員對架構有任何問題時,或是不清楚如何串連整個系統元件時,必須進行進一步的釐清,確保所有成員都知道如何完成整個實作方案。

基本上我們可以把軟體架構師的工作內容,當作提出上面這五個部份的軟體架構計畫的內容,重點是計畫內容必須符合現實的需求,要確實可行而不是天馬行空的計畫方案。

提出「簡要抽象且可行」的實作方案

一般的計畫內容,文字部份比較容易被視為是填充版面的功能,最重要的是,每一個人在打開一份計畫文件後,第一直覺就會去看架構圖,看看計畫裡面這些圖表合不合理,所以,如果有著一些鉅細靡遺的圖表,就算搭配一些不知所云的文字描述,也足夠應付一個好的計畫文件。

其實就像是在做投影片一樣,寫一堆字倒不如畫出幾個示意圖來得簡單且清楚,要注意的是,元件的關係是不能隨便畫的,每一個元件跟連接線段的關係,都要有相當且充分的理由足以支撐這個概念。

軟體架構師重點是要提出一個「簡要抽象且可行」的實作方案,計畫方案中並沒有實作的細節,但架構師必須要知道每一個部份的設計,並能提出要這樣設計的理由。

軟體架構師 vs 專案經理 vs full-stack developer

軟體架構師的工作內容看起來其實也是什麼都要會,而且很接近專案經理的角色,軟體架構師跟專案經理的差別是:

  1. 專案經理貼近客戶,軟體架構師貼近開發團隊
    專案經理的工作大都在客戶端,產出的文件大都是客戶的需求下,所需要得到的專案文件,軟體架構師則是在開發團隊中的技術 leader,產出的文件是給團隊內部實作時的參考文件。

  2. 專案經理優先考量時程跟成本,軟體架構師優先考量符合需求
    專案經理受到客戶端的壓力,所有的工作以及行為,都是以時程跟成本為前提,但軟體架構師會在時程跟成本的條件下,更注意客戶端隱藏的需求項目,這些項目常會造成專案的隱藏成本增加。

  3. 專案經理接受最小範圍的實作方式,軟體架構師增加考量未來的擴充以及延伸
    專案經理為達到目的,可以接受開發團隊用最快的方式疊床架屋,軟體架構師會考量到未來的維護以及擴充性的問題,會要求團隊遵循「適當」的原則實作。

另外有個常常聽到的名詞「full-stack developer」,聽起來也是一種什麼都要會的工作項目。 What is a Full Stack developer? 針對這個名詞,提出一個定義:full-stack developer 是一個熟悉軟體開發各個層次的工程師,對所有軟體技術有高度的興趣,但不熟悉所有的技術細節。

Full-stack 的層次包含了下面這些 layers:

  1. Server, Network, and Hosting Environment.
  2. Data Modeling
  3. Business Logic
  4. API layer / Action Layer / MVC
  5. User Interface
  6. User Experience
  7. Understanding what the customer and the business need

看著看著,跟架構師對比一下,不就是軟體架構師也要會的東西嗎?軟體架構師跟full-stack developer的差別在哪裡?

  1. 軟體架構師在意技術的限制,full-stack developer 注意軟體技術的功能
    軟體架構師跟 full-stack developer 都需要了解各個層次的軟體技術,但架構師比較注意技術本身的質量以及限制,而full-stack developer因為喜歡嘗鮮,喜歡進行各種不同的嘗試。

  2. 軟體架構師比full-stack developer更能進行抽象思考
    軟體架構師必須要提出簡要的實作計畫,需要更多抽象思考的能力,而 full-stack developer 必須要能實際上使用、操作與轉寫程式。

如何承擔軟體架構的責任

軟體架構師要能夠以宏觀的眼光來看整個系統的架構,還要能夠隨時切換自己的角色,也就是說,我可以是 SA、SD,必要時也可以是 PG。軟體架構師必須從頂端來看整個系統的概況,同時又可以縮小到關注程式碼的運作。

架構師還是要會寫程式,為什麼呢?會寫程式,才能驗證架構的可行性。更重要的是,架構師是貼近開發團隊的工作,要提出一份「可行」的架構方案,得到開發團隊的認同,所以架構師說出來的話,要能禁得起考驗。

要能得到團隊的認同,並不是耍耍嘴皮子就可以了,架構師要有足夠的技術實力,講話才會有人理你,不然也是檯面上迎合,檯面下自由發揮。

專才 specialist 還是通才 generalist?

最後來個開放式的問題:軟體架構師是個專才還是通才?

或許換個問法,專才還是通才更適合扮演軟體架構師的角色?

References

Not an Expert in All Levels of Abstraction

The 「Software Engineer」 Mindset

Software Architect – A Role, Not a Job

The full stack developer is a myth

The Full Stack Developer

如何培養架構性思考 (談軟體架構師必經之路) - 投影片分享

2015/10/05

Early Initializer in Scala

在 scala 的繼承關係中,我們有時候需要在子類別中,修改父類別中定義的變數初始值,而不是使用原本父類別初始的數值,但如果用一般的 override 語法,會發現得不到預期的結果,這時候,可以使用 early initializer 語法。

override val

這是一段很簡單的繼承的範例,當我們在 REPL 測試下列的程式時,可以發現有個變數 bar 得到的結果,跟預期的結果不同。

trait A {
    val foo: Int
    def bar: Int = 10
    println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {
    val foo: Int = 25
    println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends B {
    override val bar: Int = 99
    println("In C: foo: " + foo + ", bar: " + bar)
}

scala> new C
In A: foo: 0, bar: 0
In B: foo: 25, bar: 0
In C: foo: 25, bar: 99

val initialization rules

scala 在處理 val 變數的 initialization 以及 override,會遵循以下的規則,也因為這些條件,我們在 B 以及 A 裡面使用 bar 的時候,會得到 0 這個值。

  1. 父類別會先於子類別進行初始化
  2. 類別中定義的 members,是依照宣告的順序進行初始化
  3. 當 val 被 overridden 時,這個變數只能夠被初始化一次:這裡的意思是,我們不能在class B 中,再一次 def bar: Int = 20,compiler 會拒絕這樣的寫法,只能加上 override 覆寫 bar。
  4. 被 overridden 的 val 變數,將會在初始化父類別時,設定為預設的初始值,以 Int 來說就是 0

Eraly Initializer

如果我們希望在子類別中,賦予父類別定義的變數,一個不同於 0 的初始值,我們可以使用 Early Initialier 的語法,如同下面程式碼中的類別 C。

trait A {
    val foo: Int
    val bar = 10
    println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {
    val foo: Int = 25
    println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends {
    override val bar = 99
} with B {
    println("In C: foo: " + foo + ", bar: " + bar)
}

scala> new C
In A: foo: 0, bar: 99
In B: foo: 25, bar: 99
In C: foo: 25, bar: 99

這時候,override val bar 就會在初始化父類別之前,先初始化 bar 這個變數,因此在父類別 B 與 A 裡面,就可以使用到 bar 的初始值 99。

References

Scala Puzzlers: Now You See Me, Now You Don't

Scala: Example use for early definition / early initializer / pre-initialized fields

In Scala, what is an 「early initializer」?

Syntax for Early Definitions