2015/11/09

Text Based User Interface (TUI)

如果有用過 Linux CLI interface 的使用者,一定會看過有些 script 指令執行之後,不是直接以文字的方式輸出結果,而是跳出一個圖形界面,這種圖形界面,並不像 X Windows 那麼華麗,對使用者來說,確實獲得了不少幫助,尤其是一些比較少用到的指令,或是忘了放在哪裡的設定檔要修改的時候。

舉例來說,在 centos 如果要修改網路卡、Gateway、DNS,可以直接去修改網路卡跟 DNS 的設定檔,不常修改這些設定的使用者有另一個選擇,就是直接在 console 執行 system-config-network ,就可以看到 GUI 使用者界面,省去了不少功夫。

這種圖形界面稱為 Text-based user interface,也稱為 terminal user interface,比較複雜的 TUI 還可以做出一個完整的檔案管理員。

但有時候,我們只需要一個簡單的圖形化的使用者互動界面,這時候可以借助 dialog 的幫助,讓我們能夠快速地建造一個 TUI 界面的程式。

Dialog

Dialog 是可以直接在 console 中,產生 TUI 的 library,我們可以直接找到有關 dialog 的範例。

以 centos 來說,如果沒有安裝 dialog,可以直接用 yum install dialog 將 dialog 安裝起來。然後到 /usr/share/doc/dialog-1.1/samples/ 目錄中,可以看到很多範例程式。

[root@server ~]# cd /usr/share/doc/dialog-1.1/samples/
[root@server samples]# ls
calendar          dselect          gauge2          inputmenu3        msgbox3             slackware.rc
calendar2         editbox          gauge3          inputmenu4        msgbox4-8bit        sourcemage.rc
calendar2-stdout  editbox2         infobox         inputmenu-stdout  msgbox4-eucjp       suse.rc
calendar3         editbox3         infobox1        killall           msgbox4-utf8        tailbox
calendar3-stdout  editbox-utf8     infobox2        listing           msgbox5             tailboxbg
calendar-stdout   form1            infobox3        menubox           msgbox6             tailboxbg1
checklist         form1-both       infobox4        menubox1          msgbox6a            tailboxbg2
checklist1        form1-extra      infobox5        menubox10         msgbox-help         testdata-8bit
checklist10       form1-help       infobox6        menubox2          password            textbox
checklist2        form1-utf8       inputbox        menubox3          password1           textbox2
checklist3        form2            inputbox1       menubox4          password2           textbox3
checklist4        form3            inputbox2       menubox5          passwordform1       textbox.txt
checklist5        form4            inputbox3       menubox6          passwordform1-utf8  timebox
checklist6        form5            inputbox4       menubox7          pause               timebox2
checklist7        form6            inputbox5       menubox8          pause-help          timebox2-stdout
checklist8        fselect          inputbox6-8bit  menubox-8bit      progress            timebox-stdout
checklist-8bit    fselect1         inputbox6-utf8  menubox9          progress2           wheel
checklist9        fselect1-stdout  inputbox7       menubox-utf8      radiolist           whiptail.rc
checklist9.txt    fselect2         inputbox-both   mixedform         radiolist10         yesno
checklist-utf8    fselect2-stdout  inputbox-extra  mixedform2        radiolist2          yesno2
copifuncs         fselect-stdout   inputbox-help   mixedgauge        radiolist3          yesno3
copismall         gauge            inputmenu       msgbox            radiolist4          yesno-both
debian.rc         gauge0           inputmenu1      msgbox1           README              yesno-extra
dialog.py         gauge0-input-fd  inputmenu2      msgbox2           rotated-data        yesno-help

由 dialog 手冊,我們可看到 dialog 支援的 dialog box 有以下這些:
calendar, checklist, dselect, editbox, form, fselect, gauge, infobox, inputbox, inputmenu, menu, mixedform, mixedgauge, msgbox (message), passwordbox, passwordform, pause, progressbox, radiolist, tailbox, tailboxbg, textbox, timebox, and yesno (yes/no).

Sample 1

連續產生三個 yesno dialog,後面的 yesno 會直接覆蓋在前面的 yesno 上面。

dialog                         --begin 2 2 --yesno "" 0 0 \
    --and-widget               --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

產生三個 yesno dialog,但新的 dialog 出現前,會先把前面的清空,所以最後只會顯示第三個 yesno。

dialog           --clear       --begin 2 2 --yesno "" 0 0 \
    --and-widget --clear       --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

跟第一個範例的結果很像,後面的 yesno 會蓋掉前面的 yesno,差別是跳出這三個 yesno 之後,畫面上顯示的是第一個 yesno。

dialog           --keep-window --begin 2 2 --yesno "" 0 0 \
    --and-widget --keep-window --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

顯示第一跟第三個 yesno。

dialog           --keep-window --begin 2 2 --yesno "" 0 0 \
    --and-widget --clear       --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

Sample 2

  • hello world

    dialog --title 'Message' --msgbox 'Hello, world!' 5 20
    
  • yesno

    dialog --title "Message"  --yesno "Are you having fun?" 6 25
    
  • wait 4 seconds and exit

    dialog --infobox "Please wait" 10 30 ; sleep 4
    
  • inputbox - get result in file named "answer"

    dialog --inputbox "Enter your name:" 8 40 2>answer
    
  • textbox: file content display with scroll bar

    dialog --textbox /etc/profile 22 70
    
  • option 三選一

    dialog --menu "Choose one:" 10 30 3 1 red 2 green 3 blue
    
  • options 多選

    dialog --checklist "Choose toppings:" 10 40 3 \
          1 Cheese on \
          2 "Tomato Sauce" on \
          3 Anchovies off
    
  • radiolist

    dialog --backtitle "CPU Selection" \
    --radiolist "Select CPU type:" 10 40 4 \
          1 386SX off \
          2 386DX on \
          3 486SX off \
          4 486DX off
    
  • guage: progress bar
    將以下的 script 放到一個 test.sh 中,直接執行這個 script

    #!/bin/bash
    { for I in 10 20 30 40 50 60 70 80 90 \
        80 70 60 50 40 30 20 10 0; do
     echo $I
     sleep 1
    done
    echo; } | dialog --guage "A guage demo" 6 70 0
    

References

Linux程序設計入門 - Dialog
在 shell script 中使用圖形式互動元件 - dialog 與 zenity 使用範例
Dialog Screenshots

dialog man page

Dialog: An Introductory Tutorial

Linux Apprentice: Improve Bash Shell Scripts Using Dialog

Zenity -- 在 Shell Script 中顯示圖形使用者介面 Using GUI in shell script

ncurses

2015/11/02

RFM 模型資料分析技術

RFM 是一種使用者價值分析的工具,最常在客戶資料庫的分析上,最受零售商店以及提供服務的公司重視。利用這三種指標可以用來衡量企業與客戶之間的關係,判定每位客戶對企業來說的貢獻度,並根據這些分析的結果,擬出不同客戶階層的關係策略,這是最常用來評量客戶忠誠度及貢獻度的方法。RFM 模型是非常有用且簡單的使用者行為分析工具,能提供給企業,每一位客戶的交易資訊。

RFM

RFM 這三個指標分別是

  • 最近一次消費(Recency)
    How recently did the customer purchase?
    顧客最近一次購買至分析時間的天數,可視為顧客對企業活耀程度。

  • 消費頻率(Frequency)
    How often do they purchase?
    在一段期間內購買該企業商品的次數,可視為代表顧客對企業的忠誠度。

  • 消費金額(Monetary)
    How much do they spend?
    在一段期間內顧客購買該企業商品的金額,可代表顧客對企業的貢獻度及顧客價值。

Recency

客戶上一次來店裡面消費的時間,理論上離上一次消費時間越短的客戶,應該是最好的客戶,對即時的商品或服務最有感覺,如果業務人員需要短時間的成長業績,可以使用這個資訊。

理論上,只要能讓消費者回流一次,就能讓消費者持續來店購買東西,這也是0~6個月的客戶,會常常收到業務人員溝通訊息的主要原因。

但這也不一定是決定正確的,因為客戶來店的頻率,還必須搭配商品本身的使用週期,舉例來說,如果我們是提供零食的量販業者,如果客戶在 10/1 來店購買了1袋泡麵、2包零食,還有2罐茶飲,合理的判斷,要消耗掉這些零食的時間,可能需要2~3週,因此業務人員如果能在客戶消費後第 10~15 天,針對此客戶發送業務資訊,會是最有效的時間。

但如果客戶有購買鮮奶、麵包等保存期限比較短的食品,針對這樣的客戶,可以在一週的保存期限到期前,就對這個客戶發送這些商品的行銷DM。

Frequency

消費頻率是顧客在限定的期間內,來店購買商品的次數。消費頻率越短的顧客,也是滿意度最高的顧客。如果對品牌及商店的忠誠度越高,就代表是商店最重要的客戶。 而最常來店購買的消費者,忠誠度就越高。

如果店家想要增加營業額,其目標就是增加客戶來店的頻率,這樣的銷售方式其實也是種兩面刃,過於頻繁的業務資訊,會讓客戶產生疲乏,更糟糕的是會由愛生恨,產生厭惡感,我們必須不斷地學習,客戶會從什麼時候開始,拒絕接受店家的業務 DM。

業務行為必須「適時」、「適合」,也要「適量」,業務人員要巧妙地掌握過與不及的分寸。

Monetary

根據「帕雷托法則」(Pareto's Law),也就是 80/20 法則:80%的問題由20%的資源來解決,換句話說,公司80%的收入來自20%的顧客。

消費金額是所有數據庫報告的核心,我們可以知道排名前10%的顧客所花費的金額,也知道下一個等級者的客戶,消費的金額,每一個等級的客戶,各佔公司所有營業額的比例有多少。

如何應用 RFM

如果你的預算不多,而且只能提供服務信息給 2000 或 3000 個顧客,你會將行銷 DM 寄給貢獻40%收入的顧客,還是那些不到1%的顧客?業務人員必須以客戶的 RFM 資訊,搭配商品本身的屬性,還有一些經驗法則,是當地選擇出最有效的客戶,只針對這些客戶進行業務銷售。

綜合 RFM 三個指標,我們就可以把顧客分成五到六種不同的等級,不同的客戶等級,相對地就代表不同的銷售策略。

更重要的是,RFM 資訊所得到的客戶等級資訊是會隨著每天的時間而每天一直不斷地改變的,昨天在等級五的一般客戶,可能會因為今天的一張大訂單,一舉躍升為有價值的重要客戶。

最近一次消費、消費頻率、消費金額是測量消費者價值最重要也是最容易的方法,這充分的表現了這三個指標對銷售活動的意義。

RFD、RFE、RFM-I

RFM 的 Monetary 侷限於使用者的貢獻度,也就是使用者消費總金額的資訊,最常見的例子,就是零售或量販商店的使用者行為分析,但並不是每一個產品/專案的客戶,都會直接付錢給你。這時候,我們需要分析的使用者行為資訊,就不是金錢,而是別的目標指標。

RFD: Recency, Frequency, Duration

用來分析 viewership/readership/surfing 導向的產品的消費者使用行為。例如使用者花費在網頁瀏覽的時間的分析。

RFE: Recency, Frequency, Engagement

Engagement 可定義為 visit duration, pages pervisit 或其他指標,可用來分析使用者直接 使用 viewership/readership/durfing 導向的產品,直接跟你的產品互動時的使用者行為。

RFM-I: Recency, Frequency, Monetary Value - Interactions

用來評估如何跟使用者進行 marketing 互動的 recency 與 frequency。

References

RFM (customer value) wiki)

應用 RFM 顧客終身價值技術探討網路團購公司 顧客消費型態-A公司個案研究

RFM 模型

RFM 常用 CRM 的三維顧客分析法

RFM Customer Analysis with R Language

Calculating Customer Lifetime Value with Recency, Frequency, and Monetary (RFM)

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