助理救星-穿梭於SPSS和R之間
許哲維
前言
資料整理是分析前的必經之路,包括資料的讀取、檢核、修改、重新編碼和合併等。過去大多是透過SPSS、SAS、Stata、甚至Excel等商業軟體來完成。近來資料科學蓬勃發展,愈來愈多人受到R等開源軟體豐富的套件庫、社群互動活躍等特色所吸引,轉向使用開源軟體來進行資料分析,然而,在此同時他們卻常面臨到不同資料儲存格式造成的隔閡,不得其門而入。過去筆者擔任研究助理期間,大多時間處理sav或是dta檔,但分析資料或製圖又得仰賴R的套件,陷入常常需要花費時間上網搜尋相關套件來讀取資料,資料讀進來之後卻變成亂碼,再上網尋找解答的無限循環中。總幻想著可以博采眾軟體之長,實際卻都在做資料轉換,這或許正是許多跨越軟體使用者的寫照吧。所幸筆者在不斷爬文的過程中發現R的套件庫,有佛心開發者提供一系列的套件,幫助人們減輕檔案格式所造成的困擾,這也就是本文所要介紹的strengejacke
系列的套件,希望透過本文的介紹,可以幫助大家減少走冤枉路的時間,將更多精力放在分析上。本文將一步步帶著大家,從資料讀取開始認識此套件的使用方法,介紹一些資料處理常用到的功能,並示範簡單的資料探索、視覺化呈現,最後
套件版本說明
因為R的套件發展迅速,有時候不同版本的功能會落差,因此,若遇到錯誤訊息時,請先確認一下是否為版本差異所造成。本文所使用的套件版本為如下:xxxxxxxxxx
61## [1] "R 3.5.3"
2## [1] "sjlabelled 1.1.3"
3## [1] "sjmisc 2.8.3"
4## [1] "sjPlot 2.8.2"
5## [1] "rio 0.5.16"
6## [1] "foreign 0.8.75"
示範資料說明
本文示範的資料取自SRDA資料庫的「107年來臺旅客消費及動向調查(公共版) 」,1此為交通部觀光局於107 年1月1日至12月31日調查入境之外籍與華僑旅客(含大陸旅客,不含過境之外籍與華僑旅客),採用「配額抽樣法」抽樣,有效樣本數為7,225人。為了呈現方便本文僅選取來台旅客的人口變項、來台目的、消費金額以及搭乘大眾工具等變項作為示範。雖然本文主要目的為套件功能的介紹,但為了符合平常的使用狀況,本文仍設定了一個簡單的研究問題來模擬分析的流程,筆者希望透過這筆資料,瞭解來台目的與消費金額間的相關性,在分析的過程中,我也加入了性別、教育程度、年齡等人口變項一併觀察。
strengejacke 介紹
strengejacke系列的套件由sjlabelled
、sjmisc
、sjstats
、 sjPlot
等套件組合而成,這些套件最大的特色是如同傳統的統計軟體一樣,可以保存變項及選項的文字標籤,不僅可以提高製圖和製表的效率,也更貼近於使用傳統統計軟體的工作模式,讓人可以快速上手。本文著重分享資料處理和呈現的功能,因此只介紹前3個套件,最後的sjstats
主要應用於統計分析,若有需要的讀者可以再深入探索此套件。sj套件簡介表2
套件名稱 | 主要功能 | 主要使用指令 |
---|---|---|
sjlabelled | 讀取和處理具有標籤的資料 | read_data , set_label , set_labels , set_na |
sjmisc | 變項描述和重新編碼 | descr , rec , to_factor |
sjPlot | 快速製圖和製表 | view_df , frq |
套件安裝
使用前請先安裝sjlabelled
、sjmisc
、 sjPlot
等套件,安裝方法如下,可以選擇逐一安裝,或是如方法2,直接從strengejacke
的網頁安裝所有套件。安裝完畢別忘了使用library
匯入後才可以使用。xxxxxxxxxx
111# 方法1
2packages <- span=""> c("sjlabelled", "sjmisc", "sjPlot", "rio", "foreign"
3 )
4
5for(p in packages){
6 if(!require(p,character.only = TRUE)) install.packages(p)
7 library(p,character.only = TRUE)
8}
9
10# 方法2: 或者可以使用以下語法,一次安裝Sj家族所有套件
11devtools::install_github("strengejacke/strengejacke")
資料分析示範
讀取資料
資料分析的第一步就是讀檔了,可是面對不同格式的檔案,很可能讓人一開始就打起退堂鼓。本文以讀取SPSS的檔案作為示範,一般而言,若有SPSS的軟體,可以先輸出成csv後匯入,然而,此方法只能讀入數值,這也放棄了之前辛苦建立的標籤說明。所幸接下來介紹的sjlabelled
套件,可以幫助我們讀取具有標籤的資料,並且設計了一連串的功能供資料處理和結果呈現。首先,請先設定好工作路徑和匯入sj家族的相關套件以利後面使用,在讀取資料的階段主要使用
sjlabelled
套件。之後,我使用read_data
的功能來讀取資料檔,此功能的便利之處,在於它可以自動根據副檔名判斷讀入的檔案類型,使用者只要記得這個指令就可以吃遍sav、dta等檔案類型。xxxxxxxxxx
81setwd("D:\\") # 設定路徑
2# 匯入套件
3library(sjlabelled)
4library(sjmisc)
5library(sjPlot)
6
7# 讀取資料
8s <- span=""> read_data("data107.sav")
補充說明
網路上也介紹了其他的讀檔套件,筆者接觸過包括foreign
、haven
、rio
等套件,根據自己的使用經驗較不推薦前兩者,foreign
套件的read.spss
功能若為預設狀態,讀取資料時會自動將類別變項轉換為factor,若此時強制使用as.numeric
數值化,則可能會因為level的起始值為1,而將原先為0的資料變成1,在使用上不可不慎!另一方面,haven
套件讀入後則會將資料存為labelled
型態,此型態可能會與其他開源套件間有不相容的問題。最後rio
套件則是筆者最推薦的讀取方法,它直接將各種類型的讀檔功能打包在一起了,透過import
功能可以自動讀取各種檔案,可謂為讀檔功能的瑞士刀。
xxxxxxxxxx
21# 這個方法也可以
2r <- span=""> rio::import("data107.sav")
資料處理
標籤介紹
一份SPSS的資料檔可以分為3個部分:數據資料(數值和文字資料)、變項說明(問卷題目)、選項說明,一般套裝統計軟體已預先將3者連結,然而,在R的基本資料格式中並沒有這種儲存型態,因此需要透過sjlabelled
套件讀取標籤資料。我們在R軟體中直接檢視類別變項,呈現方式如下:xxxxxxxxxx
121> s$b1_2a
2[1] 0 NA NA NA NA NA 0 NA NA NA NA NA NA NA NA NA 0 0 0 0 4 0 NA 0
3[25] 0 NA 0 0 NA NA NA NA NA NA NA 0 NA NA NA NA 0 NA NA NA 0 NA 0
4attr(,"label")
5[1] "b1_2a 請問您來台灣前看過的台灣觀光宣傳廣告對您來台灣觀光的影響程度:報章雜誌"
6attr(,"format.spss")
7[1] "F6.0"
8attr(,"display_width")
9[1] 22
10attr(,"labels")
11沒有看過 非常低 稍低 普通 稍高 非常高
12 0 1 2 3 4 5
以b1_2a這個類別變項為例,變項數據資料將以數值的方式儲存,而變項說明和選項說明則分別儲存於attr的label和labels兩個物件中,若我們要檢視此變項的次數分配,可以使用sjmisc
套件中的frq
功能來呈現,由以下結果可見,此變項仍屬於數值變項(numeric),若後續有需要進行運算處理會較為方便,也避免格式轉換可能造成的錯誤,而在描述呈現時則會帶入標籤資料,讓人一目了然,不需要反覆去對照過錄編碼簿,如此一來可以既保有運算的彈性,又能維持呈現的可讀性。xxxxxxxxxx
131> frq(s$b1_2a)
2
3b1_2a 請問您來台灣前看過的台灣觀光宣傳廣告對您來台灣觀光的影響程度:報章雜誌 (x) <numeric>
4# total N=7225 valid N=4363 mean=0.40 sd=1.18
5
6 val label frq raw.prc valid.prc cum.prc
7 0 沒有看過 3867 53.52 88.63 88.63
8 1 非常低 36 0.50 0.83 89.46
9 2 稍低 53 0.73 1.21 90.67
10 3 普通 128 1.77 2.93 93.61
11 4 稍高 180 2.49 4.13 97.73
12 5 非常高 99 1.37 2.27 100.00
13 NA <NA> 2862 39.61 NA NA
標籤處理
認識完標籤的儲存格式後,可以進入實際的操作過程了,我們將先從修改標籤開始,過程包括:變項改名、變項說明、選項說明的調整。首先是更改變項名稱,如以下示範,原始資料為流水號命名,讓人難以直觀了解,透過rename_variables
的功能,我將其改為可以一目了然的名稱,並儲存回原本的dataframe中。之後,再使用set_label
功能將spend變項的說明替換為「總花費」,並使用set_labels
的功能定義edu變項中各個選項。xxxxxxxxxx
261# 只保留示範用的變項
2library(dplyr)
3df <- span=""> s %>%
4 select(a1, a2, b2a, c7, c7o, starts_with("d3"), e1, e4, e5, e6, e8, e9)
5
6# 變項改名
7df2 <- span=""> rename_variables(df,
8 a1 = "days",
9 c7 = "spend",
10 e1 = "nationality",
11 e4 = "age",
12 e5 = "inc",
13 e6 = "edu",
14 e8 = "sex",
15 e9 = "religion"
16)
17
18# 變項標籤
19df2$spend <- span=""> set_label(df2$spend, label = "總花費")
20
21# 選項標籤
22df2$edu <- span=""> set_labels(df2$edu, labels = c("高中職以下" = 1,
23 "大專" = 2,
24 "碩博士" = 3,
25 "其他" = 4,
26 "未回答" = 99))
缺失值定義
標籤處理完畢後,我們進一步處理資料內容,這裡就需要使用到第二個套件-sjmisc
,首先將介紹如何定義缺失值。調查資料中常會將缺失值紀錄為99,或是有「未回答」、「其他」等較難分析的答案,通常在分析時會將其排除,在R當中需要將其統一歸為NA
。為此,可以使用set_na
的功能將特定數值定義為缺失值,此功能可以支援單一變項和多重變項批次處理,甚至還可以一次處理整個dataframe的資料。如下所示,第一段語法是將age變項中的99設定為缺失值;第二段語法則是同時定義inc、edu、religion等3個變項缺失值,並且各變項的數值定義也可以不同;最後則是強制定義整個dataframe中所有變項的99為缺失值,此方法雖然方便,但要注意是否所有變項都適用此定義,以免造成分析錯誤。xxxxxxxxxx
61# 1. 將`99`設為na
2df2$age <- span=""> set_na(df2$age, na = 99)
3# 2. 批次處理
4df2 <- span=""> set_na(df2, na = list(inc = 99, edu = c(4, 99), religion = c(7, 99)))
5# 3. 處理整個dataframe
6newdf <- span=""> set_na(df2, na = 99)
補充說明
筆者在實測過程中發現,方法2(批次處理)可能會影響到其他變項的標籤,,具體而言,如範例中將edu的4定義為無效值,在此同時卻會使得資料集中其他選項4的標籤消失。筆者目前也仍未找到原因,因此為了保險起見,暫不推薦使用方法2。
變項編碼(recode)
變項類別的調整也是資料處理中重要的一步,在此使用rec
的功能,透過參數調整可以輕鬆解決各種重新編碼的問題,詳細示範可以參考官方文件的介紹,本文只會介紹幾種較常見的應用。最基本的用法是單變項的重新編碼,以下以sex變項做示範,我想將男性設為1女性則設為0,這裡有兩種寫法,首先在rec
參數中填入數值定義;之後在val.labels
參數填入新的選項說明,另一種方法則更為簡便,直接在rec
同時定義數值和選項說明。xxxxxxxxxx
91# 方法1
2df2$male <- span=""> rec(df2$sex,
3 rec = "1=1; 2=0", # 數值定義
4 val.labels = c('男姓', '女性'), # 新的選項標籤
5 as.num = T)
6# 方法2
7df2$male <- span=""> rec(df2$sex,
8 rec = c("1=1[男性]; 2=0[女性]"),
9 as.num = T)
xxxxxxxxxx
21Error in withCallingHandlers(expr, warning = function(w) invokeRestart("muffleWarning")) :
2 無效的多位元組字串於 '<94>?'
windows注意然而,若在Windows系統的電腦中使用方法2,遇到中文標籤很可能會遇到以上顯示的錯誤,應該是因為系統編碼所導致的錯誤,然而,若在Mac上則較少遇到此問題。若不幸遇上了,建議使用英文命名或是使用方法1替代。
此外在
rec
參數中還有許多不同的使用技巧,以下使用age變項做示範,其原先以每10歲為一個年齡組共有7組,我想將其縮減為3個組別。xxxxxxxxxx
61df2$agegroup <- span=""> rec(df2$age,
2 rec = c("1:2=1; 3:5=2; 6:7=3; else=NA"),
3 val.labels = c("青年", "壯年", "老年"),
4 var.label = "年齡分組", # 同時定義變項名稱
5 as.num = F # 輸出為`factor`
6 )
最後要介紹的是反向編碼的功能,這時候只要將rec
參數的位置輸入"rev"即可自動反向編碼。但需要注意的是,這個方法也很常會遇到前述的系統編碼問題,若有此狀況,只能使用前述的基本方法操作。xxxxxxxxxx
21# 反向編碼
2reverse <- span=""> rec(df2$d3b, rec = "rev")
連續變項分組
除了類別變項的調整外,連續變項的分組也是很常需要處理的問題,例如,年齡分組、收入級距、成績分組等等,在這筆資料中,我將來台旅客停留天數(days)以每10天分組,首先,使用group_var
功能在size
的位置輸入每組的大小,即可自動分組;之後再用group_labels
功能自動產生各組的標籤,再設定其level即可得到分組過後的類別變項了。xxxxxxxxxx
31df2$daygroup <- span=""> group_var(df2$days, size = 10, as.num = FALSE)
2levels(df2$daygroup) <- span=""> group_labels(df2$days, size = 10)
3frq(df2$daygroup)
xxxxxxxxxx
151a1 停留天數 (x) <categorical>
2# total N=7225 valid N=7225 mean=1.18 sd=0.66
3
4 val frq raw.prc valid.prc cum.prc
5 0-9 6461 89.43 89.43 89.43
6 10-19 507 7.02 7.02 96.44
7 20-29 158 2.19 2.19 98.63
8 30-39 41 0.57 0.57 99.20
9 40-49 19 0.26 0.26 99.46
10 50-59 19 0.26 0.26 99.72
11 60-69 5 0.07 0.07 99.79
12 70-79 3 0.04 0.04 99.83
13 80-89 11 0.15 0.15 99.99
14 90-99 1 0.01 0.01 100.00
15 <NA> 0 0.00 NA NA
資料探索
資料處理完畢後,我們就可以來探索這筆資料了,這裡則需要sjPlot
的製圖和製表功能來幫助我們快速產生報表,畢竟在探索階段也不希望花太多時間去調整輸出結果的美觀。一開始,先使用view_df
功能產生整筆資料的次數分配表,可以綜觀資料內包含的所有變項,以及初步了解資料的分佈狀況。但此功能僅會顯示類別變項,若為連續變項則會被省略表示,因此,需要再透過descr
的功能檢視連續變項的描述統計。xxxxxxxxxx
91# 只以age, inc, edu, religion等變項示範
2show <- span=""> df2 %>%
3 dplyr::select(age, inc, edu, religion)
4
5view_df(show,
6 show.na = T, # 顯示無效值個數
7 show.frq = T, # 顯示次數
8 show.prc = T, # 顯示百分比
9 )

xxxxxxxxxx
41# 連續變項描述
2descr(df2, days, spend, out = "browser",
3 encoding = "big5" # 請自行調整字元編碼
4 )

單變項次數分配
類別變項次數分配的製表,可以使用先前提過的frq
功能來檢視;至於連續變項則可透過descr
功能顯示描述統計。另外,sjPlot
套件也提供簡便的語法進行視覺化探索,雖然R軟體中早已有ggplot2
這個強大的繪圖工具,但也因為它豐富的功能使得設定上較為繁瑣,而sjPlot
則將其中幾個較常使用的圖表工具打包起來,讓使用者可以快速的使用它的套裝功能。以下以spend變項做示範,希望繪製來台旅客總花費金額的分配狀況,但因為本資料中總花費的幣值並不一致,繪圖前得先行處理,因此,本文僅保留使用新台幣消費的旅客資料,並且因為消費金額的偏態較大,我以數值加1後取自然對數的方式處理,最終使用的樣本數為2,974人。繪圖過程則使用plot_frq
的功能繪製直方圖,大家也可再透過參數設定自行選擇想要呈現的內容,這裡僅示範以normal.curve
參數呈現常態分佈曲線,若想呈現更多資訊可參考說明文件。xxxxxxxxxx
131# 保留新台幣消費資料
2df3 <- span=""> df2 %>%
3 filter(c7o == 16)
4df3 <- span=""> copy_labels(df2) # 複製標籤資訊
5
6# 取對數值處理
7ln_spend = log(df2$spend + 1)
8
9plot_frq(ln_spend, type = "hist", normal.curve = TRUE,
10 axis.title = "ln(總花費)",
11 normal.curve.color = "red",
12 normal.curve.size = 2) +
13 theme_sjplot(base_family = 'BiauKai')

補充說明
筆者在處理資料篩選的過程中,也就是filter
那段語法時發現,篩選出的新dataframe將會遺失標籤資訊,上網搜尋相關討論後,這可能是相關套件的問題。目前的權宜之計是使用copy_labels
的功能將原本資料集的標籤複製到新資料上面。
雙變項交叉表
雙變項的關係則可以透過交叉表和分組長條圖的方式呈現,我以旅客教育程度(edu)和主要來台目的(b2a)兩個變項來示範。首先,可以使用分組長條圖進行探索,下圖可見各種來台目的中不同教育程度的人數分佈狀況。繪製分組長條圖則是透過plot_grpfrq的功能,若中文標籤為亂碼,可能是因為預設字體不支援中文所致,可由theme_sjplot功能設定顯示字體,挑選電腦中可支援中文的字型即可呈現。xxxxxxxxxx
21plot_grpfrq(df2$b2a, df2$edu, show.values = F) +
2 theme_sjplot(base_family='BiauKai') # 請自行設定字體

初步的探索後,我希望可以更深入了解這兩個變項間的關係,因此我使用
tab_xtab
的功能來呈現兩變項的交叉表, 並且透過調整show.row.prc
和show.col.prc
兩個參數,可以呈現出交叉表中的列和欄的百分比(紫色和綠色的數字),若想要呈現期望值請另外進行參數的調整。xxxxxxxxxx
21# 製表
2tab_xtab(df2$edu, df2$b2a, show.row.prc = T, show.col.prc = T, encoding = "UTF-8")

題組探索
最後則是介紹題組題的探索分析,題組可說是調查資料獨特的問題型態,但要如何清楚的呈現並不容易,所幸本套件也有提供快速的圖表製作功能,本文選用「請問您這次在臺灣期間搭乘的交通工具滿意程度」的題組進行示範,首先使用find_var
功能將D3題組統一匯出為新的dataframe,並將選項0沒有搭乘設為無效值。之後使用tab_stackfrq
的功能,再依參數要求填入選項和變項說明,即可呈現各題項的回答狀況了,但要注意的是若資料有na,則須設定參數show.na = True
,否則會出現錯誤訊息。xxxxxxxxxx
31# 資料處理
2like <- span=""> find_var(df2, pattern = "d3", out = "df") # 挑選D3題組
3like <- span=""> set_na(like, na = 0) # 未搭乘設為無效
xxxxxxxxxx
91# 選項標籤
2levels_5 <- span=""> c("非常不滿意", "不滿意", "普通", "滿意", "非常滿意")
3
4# 變項名稱
5items <- span=""> names(like)
6
7tab_stackfrq(like, value.labels = levels_5, var.labels = items, show.na = T,
8 encoding = "big5")
9

plot_likert
的功能則可以視覺化呈現整個題組的回答狀況,讓研究者可以更快速的了解各題目和選項的分配差異,由以下結果可以明顯地觀察到大多數受訪者的回覆都是偏向滿意一側。但使用此功能需要注意有沒有設定catcount
-選項數的參數,若沒有設定由套件自動偵測會有很高的出錯機會。此外,若題目中包含中立選項例如,不知道、沒意見等,可在cat.neutral
設定,套件則會將其呈現在最後方,避免影響次序的比較。xxxxxxxxxx
51plot_likert(like,
2 catcount = 5, # 選項數
3 #cat.neutral = value, # 中立選項的值
4 values = "sum.outside",
5 show.prc.sign = TRUE)

模型檢視
資料探索完畢後,本文以線性迴歸模型的呈現收尾。筆者建立一個簡單的模型來觀察人口變項、來台目的和消費金額間的關係。如同前述,為了幣值的統一,這裡只保留使用新台幣消費的旅客樣本(n = 2,954)。首先,使用as_factor
的功能將類別變項定義為factor的型態,在R的迴歸模型中會自動將factor以虛擬變項處理,不需要另外設定 (若想要更改參考組請使用ref_lvl
)。但這裡也得提醒大家,as_factor
會受到電腦系統的字元編碼的問題影響產生錯誤,尤其在Windows系統中較為常見,筆者目前也仍未找的有效的解決辦法。回到示範,模型1放入人口變項,模型2則加入來台主要目的(b2a)。之後使用tab_model
的功能,並透過show.reflvl
選擇是否顯示參考組,當然,這個功能也提供了許多的輸出版型,使用者可以從官方介紹裡面尋找符合自己需求的輸出樣式。另外,也順便補充一個實用的輸出模板-stargazer可以交替使用。xxxxxxxxxx
51# 轉為factor
2df3 <- span=""> as_factor(df3, sex, religion, edu, inc, age, b2a)
3m1 <- span=""> lm(ln_spend ~ sex + edu + age, data = df3)
4m2 <- span=""> lm(ln_spend ~ sex + edu + age + b2a, data = df3)
5tab_model(m1, m2, show.reflvl = T)

最後則是以視覺化圖形來呈現迴歸分析的結果,這裡使用
plot_model
的功能,將模型放入後會依照迴歸係數繪製出以下圖形,可以更直觀地看出個變項的顯著性和信賴區間。xxxxxxxxxx
31plot_model(m2, vline.color = "red", show.values = TRUE,
2 value.offset = .3) +
3 theme_sjplot(base_family = 'NotoSansCJKtc-Black')

結語
總體而言,透過strengejacke
系列套件,可以降低不少跨軟體轉換的阻礙,讓人可以一方面享受開源軟體多樣且新穎的功能,並且語法操作也更具彈性,有助於面對不同樣貌的資料;另一方面也可以接軌傳統統計軟體累積的豐富資料。然而,實際上的操作絕不是像前面介紹般的輕描淡寫,並非照著範例把語法寫完就可以拿到輸出成果了。就筆者的經驗來說,遇到最多就是字元編碼的問題,這個問題在Windows平台上尤為嚴重,筆者將本文的語法在Mac上執行都沒問題,但卻在Windows上遭遇到一些錯誤,遇上這種狀況也只能見招拆招,若有更好的解決方法也歡迎提供分享。最後還是得強調,本文僅簡略介紹了sj套件家族中的常用功能,其他還有許多實用的功能和技巧,可以協助使用者應對各種資料處理問題,若想有更深入的認識,推薦大家可以閱讀劉正山老師的《民意調查資料分析的R實戰手冊》,可以更完整的學習如何使用這些套件處理和分析調查資料。此外,筆者也推薦大家可以造訪下列幾個套件的官方網站,裡面都有各個功能的範例與參數說明,也可以參考作者的教學文件,這些都可以幫助大家更快了解這些套件的運作。
- sjlabelled - Labelled Data Utility Functions
- sjmisc - Data and Variable Transformation Functions
- sjPlot - Data Visualization for Statistics in Social Science
- sjstats - Collection of Convenient Functions for Common Statistical Computations
留言
張貼留言