調職合法性的實證研究分析

Legal Analytic of Employee Transfer

廖弘州 HUNG-CHOU, LIAO

李紹嘉 SHAO-CHIA, LI

蔡宗霖 TSUNG-LIN, TSAI

張弘軒 HONG-SYUAN, CHANG

Back to the Class Page

In [1]:
import os
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.model_selection import train_test_split, KFold
import numpy as np
import matplotlib.pyplot as plt
import gspread
# %matplotlib inline
from sklearn import tree
from sklearn.model_selection import train_test_split
from sklearn import metrics
# seaborn
import seaborn as sns
In [2]:
from google.colab import auth
auth.authenticate_user()
import gspread
from oauth2client.client import GoogleCredentials
gc = gspread.authorize(GoogleCredentials.get_application_default())
from sklearn.linear_model import LinearRegression
from sklearn.feature_selection import f_regression
from sklearn import preprocessing, linear_model
import graphviz

from IPython.display import clear_output
clear_output()
In [3]:
wb = gc.open_by_url('https://docs.google.com/spreadsheets/d/1PMsNIYFxGil51lUIaJ5VAAFHiI1UhCVulhq02nn6yX0/edit#gid=498313589')
sheet = wb.worksheet('main 3')
data = sheet.get_all_values()
df = pd.DataFrame(data)
df.columns = df.iloc[0]
df = df.iloc[1:]

#df.head()
In [4]:
#df.info()
#df.describe()
#df.head()
In [5]:
#告訴電腦標註的形式 字串(str)、數值小數(float)、整數(int)、不相關種類(str)、有序種類(int)
#str int float
new_df_schema = {
'INDEX': df['INDEX'].astype(str),
'JID': df['JID'].astype(str),
'JCOURT': df['JCOURT'].astype(str),
'JDATE': df['JDATE'].astype(str), 
'JTITLE': df['JTITLE'].astype(str), 
'var_y': df['var_y'].astype(int),
'A1': df['A1'].astype(int), 
'A2': df['A2'].astype(int),
'A3': df['A3'].astype(int),
'A4': df['A4'].astype(int),
'A5': df['A5'].astype(int),
'B1': df['B1'].astype(int),
'C1': df['C1'].astype(int),
'C2': df['C2'].astype(float),
'C3': df['C3'].astype(int),
'D1': df['D1'].astype(int),
'D2': df['D2'].astype(int),
'E1': df['E1'].astype(int),
'E2': df['E2'].astype(int),
'F1': df['F1'].astype(int),
'F2': df['F2'].astype(int),
'G1_0': pd.get_dummies(df.G1)["0"].astype(int),
'G1_1': pd.get_dummies(df.G1)["1"].astype(int),
'G1_2': pd.get_dummies(df.G1)["2"].astype(int)
}
In [6]:
df = pd.DataFrame(new_df_schema)
In [7]:
#df.info()

一、研究動機

勞動基準法第1條規定,為規定勞動條件最低標準,保障勞工權益,加強勞雇關係,促進社會與經濟發展,特制定本法。由此可知,勞基法之制定主要目的在於強化勞工與雇主間之關係,解決勞資雙方糾紛。而於實務上,雇主未經勞工同意而任意調動勞工職務,是勞資糾紛之主要原因之一。且調職後勞工因而有薪資變更、拒絕上班或因各種因素無法勝任工作等等情形,更牽涉後續請求資遣費或薪資或雇主得否合法解雇等問題,而其先決問題均在於當初之調職究竟是否合法。因此,於何種情況下調職會被認為合法或不合法,不論是對於雇主或勞工而言,均是勞基法下之重要問題,本文研究之動機源自於此。

二、議題設定

對於調職合法性之判斷,在105年勞動基準法第10條之1增訂前,內政部於74年9月5日內勞字第328433號函釋揭露了所謂"調職五原則",而司法實務上有直接採用該標準予以判斷者,亦有採取抽象的「合理性」、「正當性」及「必要性」標準,抑或兼採之者。而在105年勞動基準法第10條之1增訂後,將調職五原則明文化,並新增了第5款考量勞工及其家庭生活利益的部分,明文規定:
雇主調動勞工工作,不得違反勞動契約之約定,並應符合下列原則:
一、基於企業經營上所必須,且不得有不當動機及目的。但法律另有規定者,從其規定。 二、對勞工之工資及其他勞動條件,未作不利之變更。
三、調動後工作為勞工體能及技術可勝任。
四、調動工作地點過遠,雇主應予以必要之協助。
五、考量勞工及其家庭之生活利益。

雖然該條各款與單純之"合理性"、"必要性"或甚至"社會通念"等抽象標準比起來,已屬比較具體的規定了,但若單純觀察條文仍無法得知法院在適用本條時究竟具體、細部考量了哪些因素(比如要有如何之業務業績或營運情況才能認為係"基於企業經營所必須"?或多長之通勤距離或多高之通勤成本才會認為"工作地點過遠"?,並因此進一步認定是否已滿足"必要之協助"?)。又這些因素彼此間是否有優先劣後之關係,亦即哪些因素是對法院心證影響最大的、或是否有特定因素法院並不是那麼在意而對判決結果不具重要性?這些問題是本文想要透過資料分析而探求的。

三、文獻回顧

我國目前與勞動法領域相關之實證研究並不多,本文僅找到以改革現行勞動訴訟制度為目的所做關於勞動訴訟之實證研究(註1)一則,然於本文探討之議題關係不大。

而其他與「調職合法性」相關之文獻多作於勞動基準法10-1條增訂(104年12月16日)以前。而於舊法時代,學者(註2)指出,實務上大致可分為三種判斷模式:(1)逕以內政部74年所頒佈之台內勞字第328433號函釋(即過去俗稱之調職五原則)為依據;(2)以抽象的「合理性」、「必要性」為標準;(3)兼採(1)與(2)者。而於(3)中,存在(1)與(2)關係不明之疑慮。

學者間在過去亦就調職五原則的定位多有爭論,其究係作為雇主調職命令權限之來源,抑或作為調職命令權之控制手段,模糊不清(註3)。且函令之用語(如「過遠」、「必要」、「必需」)亦過於空泛無益於實際判斷,而招致批評。

於104年修法時,立法者於立法理由(註4)明確說明勞基法10-1條各款作為權利濫用禁止原則之規範而訂定 ,且基於肯定雇主應留意勞工工作與生活的平衡(註5)而增訂第5款「考量勞工及其家庭之生活利益」。故而,勞基法10-1條適用之情況,應屬確認雇主具調職命令權限後所為判斷。

關於5款之內容為何,修法後之文獻(註6)除有就第1款「企業經營所必需」之調職區分為以下狀況:經營必要型、迴避資遣型、懲戒型調職、女工妊娠期間之調職、職災復原期間、因健康檢查結果所為之調動。亦有針對各款提出一些可能該當的間接事實,第1款可判斷人選選擇(如勞工是否單身);第2款包括薪資減少、工時增長、工作型態改變、工作環境改變;第3款包括雇主有無提供職業訓練等必要協助;第4款包括以單趟通勤時間是否超過一小時判定是否過遠;第5款包括是否需照顧12歲以下兒童或65歲以上老人。


註1:黃國昌,第4章:我國勞動訴訟之實證研究──以第一審訴訟之審理與終結情形為中心,程序法學的實證研究,頁129-217(2012年)

註2:林更盛,雇主調職權限的控制── 最高法院九十八年度台上字第六○○號判決評析,月旦裁判時報,20期,頁7-10(2013年)

註3:有學者另認為(舊)調職五原則僅供區別「調職」與「雇主變更勞動契約內容之要約」,而與雇主調職命令權限之依據或是否有權利濫用無關。參照:邱駿彥,調職之法理運用,輔仁法學,15期,頁265-267(1996年)

註4:委員蔣乃辛等24人提案:「…二、本席等認為,雇主調動勞工工作除不得違反勞動契約之約定外,尚應受權利濫用禁止原則之規範,爰增訂本條文,明訂雇主調動勞工職務不得違反之五原則。三、在第一項第一款中所謂「不當動機與目的」,例如部分企業為迫使員工離職,故意將工作地點從南調到北,或故意減少薪資,降低勞動條件,迫使員工無奈請辭。類似的案列與企業經營所需無關必須禁止。四、勞工因事業單位調動所受可能之不利益已列入於第一至第四款,但亦可能產生育兒及照顧家人等家庭生活之不利益,尤其是每天12 歲以下之幼童上下課與保姆家接送及65 歲以上老人之照護,且目前已有很判例支持,如高雄地院88年勞訴字第39號判決、高等法院台中分院92 年勞上易字第40 號判決及台灣高院94 年勞上易字第96號判決,皆肯定雇主需留意勞工家庭生活育兒及照顧家人之問題,爰增訂第五款。」來源:立法院公報,104卷89期,頁350-363

註5:過去有學者提出過度重視勞工個人情事,反而可能帶來企業內之人事不公正。參照:邱駿彥,調職之法理運用,輔仁法學,15期,頁264(1996年)

註6:陳旺聖,調職糾紛爭議之研究,勞動及職業安全衛生研究季刊,27卷4期,頁124-127(2019年)

四、研究方法

(一)研究樣本

本組以相關法條「勞動基準法 10-1條」鍵入法源法律網裁判書查詢系統,將範圍設定在修法後之105年至109年間,並為避免同一案例事實被重複判斷,因此並未納入高等法院及最高法院之裁判而僅鎖定在地方法院,嗣得出391件,扣除其中為上訴審及不相關者,(如:法院認定為委任關係不適用勞基法、調職合法與否非訴訟爭點,法院未實質判斷者等),並按每一人每一次調職作為一筆資料之方式,最後共計274筆。

(二)變項設計

本組起初透過裁判閱讀之方式,將各裁判中於判決理由中所提及影響法院認定調職合法性之因素挑出,共計47欄變項,惟因變項大多過於個案性,以致於法院未提及之變項數過多,故於剔除有提及比例小於3%(如:雇主是否變更)及過於模糊寬泛之變項(如:是否在社會通念容忍程度內)後,整併概念相似者,並依其係符合勞基法第10-1條何款之構成要件分類,A對應第1款「企業經營所必須」;B對應於第1款之「不當動機」,C對應於第2款「勞工之工資及其他勞動條件」,D以後分別各自對應於3款以降。又為觀察是否委任律師對結果是否會造成影響,增設非屬構成要件而為客觀事實之「G1」。其中G1之編碼方式,不同於其他變項係以「是=1;否=-1;未提及=0」,而是以「無律師=0;一般律師=1;法扶律師=2」,並於後述為建立回歸模型,將屬類別變項之G1區分為「G1_0」(無律師)、「G1_1」(一般律師)、「G1_2」(法扶律師)以「進行測試。

其中部分變項之設定出於一些考量亦較原先有所調整,如為凸顯變項之改變,在通勤成本是否增加,原先雖以「增加幅度是否超過通勤時間一小時/通勤距離10km」作為區分1及-1之標準,但符合1之樣本數過少,為使概念上落入屬有增加的「1」之樣本變多,本組後將是否增加之標準改為「增加幅度是否超過通勤時間半小時/通勤距離5km」,希望能看出通勤成本增加一定程度對法院判斷所造成的影響。又如因部分變項存在主觀判斷空間,於組員間判斷出現歧異,為避免此類爭執,將其改為客觀判定之用語,以求判斷趨於一致,將調職前後工作內容是否「相似」改為「相同」即為一例。

變項編號 意義 說明
A1 雇主是否業務緊縮、業績下滑 是=1;否=-1;未提及=0
A2 勞工工作表現是否不佳(職場人際關係、工作業績、不聽上級指示) 是=1;否=-1;未提及=0
A3 是否為整體性調整(擴廠、廠址遷移、部門裁減) 是=1;否=-1;未提及=0
A4 雇主有無提供替代性方案(有無多個不同部門、工作地點供勞工選擇) 是=1;否=-1;未提及=0
A5 維持客戶與雇主之關係(影響公司商譽、管委會) 是=1;否=-1;未提及=0
B1 是否出於與職務無關之動機(比如勞工的宗教信仰、或女性勞工懷孕、勞工或雇主間之前是否有勞資糾紛) 是=1;否=-1;未提及=0
C1 工資是否減少 是=1;否=-1;未提及=0
C2 有無降級、降職 是=1;否=-1;未提及=0
C3 調動環境是否為正常工作環境 是=1;否=-1;未提及=0
D1 工作之性質、內容是否相同 是=1;否=-1;未提及=0
D2 加重職務負擔(新工作是否須具備特殊體能及專業知識、工作內容變多、體能負擔變重、窒礙難行) 是=1;否=-1;未提及=0
E1 通勤成本 通勤時間增加多於30分鐘/距離增加多於5KM=1;否=-1;未提及=0
E2 住宿、交通或其他之金錢或實物輔助 是=1;否=-1;未提及=0
F1 是否不利於勞工作息安排 是=1;否=-1;未提及=0
F2 是否需照顧家人、配偶、長輩 是=1;否=-1;未提及=0
G1 勞工是否受律師協助 無律師=0;一般律師=1;法扶律師=2"

五、敘述統計

In [8]:
# Colab 進行matplotlib繪圖時顯示繁體中文
# 下載台北思源黑體並命名taipei_sans_tc_beta.ttf,移至指定路徑
!wget -O taipei_sans_tc_beta.ttf https://drive.google.com/uc?id=1eGAsTN1HBpJAkeVM57_C7ccp7hbgSz3_&export=download
!mv taipei_sans_tc_beta.ttf /usr/local/lib/python3.7/dist-packages/matplotlib//mpl-data/fonts/ttf

from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt 

# 自定義字體變數
myfont = FontProperties(fname=r'/usr/local/lib/python3.7/dist-packages/matplotlib/mpl-data/fonts/ttf/taipei_sans_tc_beta.ttf')

# !!!!後續在相關函式中增加fontproperties=myfont屬性即可!!!!
clear_output()

(一)案件類型

本組樣本數共274件,均來自於地方法院民事庭之判決。本組為避免同樣的案例事實被重複判斷,因此並未納入高等法院及最高法院之判決。

螢幕快照 2021-05-22 下午5.04.37.png

從上圖可以看出,涉及勞基法10-1條之案由最多者為給付資遣費,其他較常出現的裁判案由為確認僱傭關係存在、給付薪資、給付工資。法院依照勞基法10-1條判斷調職合法於否僅係作為判決中之一個重要爭點。

(二)案件管轄法院分佈

上述折線圖統計了各法院對於調職合法性的判決總數量,其中案件量多集中於北部,以臺北地院48例、桃園地院40例、新北地院39例最多,而中南部地區則以高雄地院、台中地院判決數量最多

螢幕快照 2021-05-22 下午5.30.36.png

(三)判決結果

在本組分析之274個案件之中,最終判決結果雇主調動員工合法的案例總數共佔約60%,而調職不合法的案件約佔40%。

In [9]:
import matplotlib.pyplot as plt

plt.pie(df["var_y"].value_counts(),labels = ['合法','不合法'], autopct='%1.1f%%',textprops=dict(fontproperties=myfont))
plt.title('調職合法性',fontproperties=myfont)
plt.axis('equal')
plt.show()

(四)各變項分布情形

In [10]:
df_bar = df.iloc[:,5:]
df_bar = df_bar.melt(id_vars = "var_y", var_name='x')
df_bar.head()

sns.set_theme(style="ticks")


f, ax = plt.subplots(figsize=(20, 10))
sns.despine(f)

sns.histplot(
    df_bar,
    x="x", hue="value",
    multiple="stack",
    palette="YlGnBu",
    edgecolor=".3",
    linewidth=.5
)
Out[10]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fc82259b7d0>

上方長條圖呈現了每一個變項填選的數值與分部的比例,除了G1(原告有無法律協助)此一變項是以0,1,2作為選填結果之外,其他變項都是以1代表「是」,-1代表「否」,0代表「法院未提及」。從上圖可以得知每一變項的數值比例差異大,以C1(有無調降薪資)為例可以看出1,-1及0各佔總案件數的三分之一,而以E2, F1, F2為例,其法院未提及者佔大多數,本文認為此三變項可能較具有個案性質,未必於大部分案件均會出現。 F1, F2是立法新增的(考量勞工及其家庭之生活利益),學者認為可以參考後續實務操作,從上述圖可以看出實務還是沒有很常判斷這款,但F1(是否不利作息安排)是本文後續判決重要的變項(詳後述),因此只要一出現就很容易影響判決結果。 至於其未出現的的理由是勞工較少將此作為攻擊防禦方法,或法院較少判斷。

(五)判決日期

螢幕快照 2021-05-24 下午7.38.33.png

如上圖,可以看出自105年至109年涉及勞基法10-1的判決數量逐年上升,因為本組只搜集案號為105年度到109年度的判決,觀察到此現象,本文提出兩種可能的解釋: 1.修法後,權益的保障比較明確,且法院必須依據本條考慮調職是否合法,因此可能勞工比較願意依據本條提起訴訟,而使訴訟案件增多 2.另外,可能修法後存在法律適用的過度期,法院在105, 106年引用勞基法10-1的案件量逐漸增多,但是這個過渡期究有多久,能否解釋108, 109年的增加仍有疑問

螢幕快照 2021-05-24 下午7.38.45.png

(六)工資調降而調職合法的情形

按照勞動基準法第10-1條第1項第2款規定「對勞工之工資及其他勞動條件,未作不利之變更。」是否對勞工之工資有不利的變更為調職五原則明文之重要判斷標準,理論上對於雇主對於勞工工資有作不利變更者將會被認定為調職不合法。 然而有趣的是,我組發現在員工調動之後工資有調降的共87個案例中,仍有42個案例最後被法院認定為調職合法,因此即使在工資有調降的情形,也未必代表調職一定不合法,法院仍然會以其他因素綜合判斷最終調職是否合法。 因此我組嘗試找出在工資確有調降之情形下,法院究依據哪些因素來判定最終調職仍然合法?並嘗試分析因素之間的關聯與重要度。

1.工資調降與員工表現不佳之關係

四象限圖.png

上圖係員工表現不佳與工資調降之間的關聯圖,其中我們研究的對象是調職後工資有調降但同時被調職之員工有表現不佳的情形共有23個案例(綠色部分),在這23案例之中經過統計,共有21例被法院認定調職合法,而僅有2例被認為調職不合法,由此本文可以歸納出,若勞工工資有調降但同時有發生勞工表現不佳的情形,最終判決大部分都還是以勞工表現不佳作為雇主調職合法的理由。

2.工資調降與整體性調整之關係

四象限圖2.png

上圖係總體性調整與工資調降之間的關聯圖,其中我調職後工資有調降但同時公司有進行整體性調整的情形共有16個案例(綠色部分),在這15案例之中經過統計,共有13例被法院認定調職合法,而僅有2例被認為調職不合法,由此本文亦可以歸納出,若勞工工資有調降但同時有發生整體性調整的情形,最終判決大部分都還是以勞工表現不佳作為雇主調職合法的理由。

根據上述的分析,本文足以得出結論,就勞工工資遭調降而調職卻合法的案例中,法院常以員工是否表現不佳及公司是否進行整體性調整作為調職合法的理由(佔34/42)。

六、結果呈現

(一)相關

In [11]:
Correlation = pd.DataFrame(df[[
       'var_y', 'A1', 'A2', 'A3',
       'A4', 'A5', 'B1', 'C1', 'C2', 'C3', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2','G1_1','G1_2'
       ]])#自己調
 
colormap = plt.cm.viridis
plt.figure(figsize=(20,16))
plt.title('all corelations', y=1.05, size=15)
corr = Correlation.astype(float).corr().round(2)
sns.heatmap(corr,linewidths=0.1,vmax=1.0, square=True, cmap="YlGnBu", linecolor='white', annot=True)
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fc821655550>

上開圖表判讀,顏色愈趨於深藍/淺黃者,正相關/負相關性越高。

試以圖表當中上相關性大(即絕對值最高者)之變項為簡單說明:

  1. D2 v. Var_y = -0.63 →有加重職務負擔,最容易被認為調職違法
  2. B1 v. Var_y = -0.54 →雇主出於與職務無關之動機調職,次容易被認為調職違法
  3. A1 v. A3 = 0.48 →雇主有業務緊縮或業績下滑通常伴隨著整體性調整出現

而與本文所欲聚焦觀察的依變項var_y(調職是否合法)相關性最高的前六個自變項分別為:D2(是否加重職務負擔), B1(是否出於與職務無關之動機), F1(是否影響勞工作息安排), C1(是否減薪), A1(雇主是否業務緊縮、業績下滑) , A3(是否為整體性調整)

In [12]:
import os
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.model_selection import train_test_split, KFold
import numpy as np
import matplotlib.pyplot as plt
import gspread
# %matplotlib inline
from sklearn import tree
from sklearn.model_selection import train_test_split
from sklearn import metrics
# seaborn
import seaborn as sns

(二)馬賽克圖

In [13]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(15,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['B1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["B1"] = df1["B1"].apply(lambda x: "inappropriate_yes" if x == 1 else ("inappropriate_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['B1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['B1','var_y'],properties=props, labelizer=labelizer)

plt.show()
/usr/local/lib/python3.7/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  import pandas.util.testing as tm
p value is 4.230714629604166e-18
Dependent (reject H0)

雇主動機是否與職務無關,扮演重要性因素,若法院有提及,則無關者有高度蓋然性被認定為違法。

In [14]:
6##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(15,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['F1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["F1"] = df1["F1"].apply(lambda x: "hard_yes" if x == 1 else ("hard_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['F1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['F1','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 7.2285842674580575e-09
Dependent (reject H0)

是否影響勞工作息安排,亦為重要性因素,雖有提及之比例不高。但若法院有提及,則無關者有高度蓋然性被認定為違法。

In [15]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['C1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["C1"] = df1["C1"].apply(lambda x: "less_yes" if x == 1 else ("less_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['C1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['C1','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 1.3560581441869557e-10
Dependent (reject H0)

從勞基法10-1條之文義觀之,若雇主之情況不符五款之一,即應判決不合法。惟從數據呈現結果來看,「減薪」雖不符合勞基法10-1條,但自圖表中最左欄被判決合/不合法的比例係為各半。 返回文本,此些有減薪卻合法的調職,通常伴隨「勞工表現不佳」,此一該當「企業經營所必須」的因素出現,返回文本,法院於法學解釋下會認為此時的薪資減少「無不利之變更」,可見法院實質上仍係參酌個案中利益衡平,對於調職五原則之適用作出調整修正,而非一慮僵化操作勞基法10-1款。 實則勞工表現不佳而被認為減薪合法之結果,除較合乎一般社會通念,和過去實務見解亦相符。(最高法院98年度台上字第600號判決:查雇主調動勞工工作,應斟酌有無企業經營之必要性及調職之合理性,倘勞工擔任不同之工作,其受領之工資當有所不同,尚不得僅以工資總額減少,即認調職違法。) 自此角度觀之,勞基法10-1款至多是提供法院於評斷調職合法時可供評斷的間接事實,而並非強制性規定,違反其一調職即不合法,毋寧是各間接事實應被綜合評價,實際上係雇主與勞工之利益衡量,再依經驗法則得出有無權利濫用及調職之必要性,此亦與調職五原則的提出,本係作為調職權限限制之法律性質相符。

In [16]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['A3'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df[''] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["A3"] = df1["A3"].apply(lambda x: "whole_yes" if x == 1 else ("whole_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['A3'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['A3','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 2.203197421370655e-06
Dependent (reject H0)

若為整體性調整(如擴廠、廠址遷移、部門裁減),則法院多數認為調職合法。 若非屬整體性調整,則應視個案中其他情況(如勞工表現是否不佳、是否減薪、調動過遠等),故而合法性比例約屬各半。

In [17]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['A2'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["A2"] = df1["A2"].apply(lambda x: "bad_yes" if x == 1 else ("bad_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['A2'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['A2','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 4.08108341478395e-08
Dependent (reject H0)

若勞工表現不佳,則有八成調職會被認為是合法。

In [18]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['D2'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["D2"] = df1["D2"].apply(lambda x: "heavy_yes" if x == 1 else ("heavy_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['D2'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['D2','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 8.313716333501128e-25
Dependent (reject H0)

若加重職務負擔(增加勞工之工作內容、體能負擔、新工作需具備特殊體能及專業知識),則法院大多認定調職不合法。

In [19]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['D1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["D1"] = df1["D1"].apply(lambda x: "same_yes" if x == 1 else ("same_no" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['D1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['D1','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 4.211237153476317e-11
Dependent (reject H0)
In [20]:
from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['G1_1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1['G1_1'] = df1['G1_1'].apply(lambda x: "yes" if x == 1 else ("no" if x == 0 else x ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['G1_1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['G1_1','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 0.4654381110894269
Independent (H0 holds true)

勞工有律師協助的勝訴比率反而比較低。惟律師之協助是否未形成助益並無法直接斷論,蓋亦有可能是較為棘手(本來就不易勝訴之案件)勞工才聘請律師。且兩者勝訴之比例之差距並不明顯。

In [21]:
##比較美(?)一點點的馬賽克圖

from statsmodels.graphics.mosaicplot import mosaic
from scipy import stats

plt.rcParams["figure.figsize"]=(10,8)
plt.rcParams.update({'font.size': 22})

crosstable=pd.crosstab(df['E1'],df['var_y'])#改變數
stat, p, dof, expected =stats.chi2_contingency(crosstable, correction=False)
#crosstable
# interpret p-value
alpha = 0.05
print("p value is " + str(p))
if p <= alpha:
    print('Dependent (reject H0)')
else:
    print('Independent (H0 holds true)')

props={}
lab = {}
#df1 = df[df['D2'] != '0']  #剔除不適用此變項的資料列
df1 = df.copy()
df1["E1"] = df1["E1"].apply(lambda x: "more than_5k" if x == 1 else ("less than_5k" if x == -1 else "not_mention" ))   #變更x10數據值的標籤
df1["var_y"] = df1["var_y"].apply(lambda x: "legal" if x == 1 else ("illegal" if x == 0 else x ))  #變更y2數據值的標籤
crosstable1=pd.crosstab(df1['E1'],df1['var_y'])
col = ['xkcd:aqua','lightblue','red']   #顏色名稱資料庫:https://www.w3schools.com/cssref/css_colors.asp
n = 0

for i in crosstable1.columns:
  color = col[n]   #y2值相同的塗上相同的顏色
  m = 0
  for j in crosstable1.index:
    #color = col[m] #x10值相同的塗上相同的顏色
    total = sum(crosstable1.iloc[m])
    props[(j,i)]={'facecolor':color, 'edgecolor':'white'}
    lab[(j,i)] = f"{crosstable1.iloc[m,n]}, {round(crosstable1.iloc[m,n]/total *100,2)}%"
    m += 1
  n += 1

labelizer=lambda k:lab[k]
mosaic(df1,['E1','var_y'],properties=props, labelizer=labelizer)

plt.show()
p value is 3.2366956517550827e-06
Dependent (reject H0)

1.依照常理,通勤成本(距離)應為調職合法與否的重要判斷因素,然而依據我們後續的研究發現,通勤成本並未被機器判別為影響結果的重要因素之一

2.而依照上述馬賽克圖,就通勤成本有變動的數據來看(除去未提及的),若通勤距離少於5公里,則有高達八成七的判決會認為調職是合法的;相反地,若高於5公里,則仍有半數左右的判決會認為調職是合法,本組以判決為基礎,推論可能係因為法院認為仍在社會通念容忍程度。加以變項未提及的比例接近一半,因此距離對於調職合法性的判斷可能未具有特別的重要性。

(三)迴歸

1.樣本一 全部變項

(1). 模型

In [22]:
from sklearn. linear_model import LinearRegression
from sklearn import linear_model
from sklearn import preprocessing, linear_model

X = df[[ 'A1','A2','A3','A4','A5','B1','C1', 'C2', 'C3', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2','G1_1', 'G1_2' ]] #四個想要解釋的變數
y = df['var_y']  #y 注意數值或是二元
#y = df['var_y']

#lm = LinearRegression() #如果是數值y
lm = linear_model.LogisticRegression() #如果是二元y

lm.fit(X, y)
Out[22]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)

跑回歸的意義,在於設立兩個模型。(假設是有D2的模型和沒有D2的模型), 來看有和沒有D2之間的差異有多大。若p-value越小,也就是這個變項的顯著性越大, 通常可以認定此變項影響y的程度很大。

In [23]:
#線性迴歸

#加截距項

import statsmodels.api as sm 
df_Logit=df.copy()
#print(df_Logit["intercept"])

X_Logit = sm.add_constant(df_Logit[['A1','A2','A3','A4','A5','B1','C1', 'C2', 'C3', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2','G1_1', 'G1_2']])
y_Logit = df_Logit['var_y'] 


from statsmodels.api import Logit
print(Logit(y_Logit,X_Logit).fit().summary())
Optimization terminated successfully.
         Current function value: 0.216275
         Iterations 9
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                  var_y   No. Observations:                  273
Model:                          Logit   Df Residuals:                      255
Method:                           MLE   Df Model:                           17
Date:                Tue, 15 Jun 2021   Pseudo R-squ.:                  0.6778
Time:                        08:23:41   Log-Likelihood:                -59.043
converged:                       True   LL-Null:                       -183.23
Covariance Type:            nonrobust   LLR p-value:                 4.463e-43
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const         -1.2650      0.621     -2.038      0.042      -2.481      -0.049
A1            -1.2197      0.612     -1.992      0.046      -2.420      -0.020
A2             1.8263      0.519      3.518      0.000       0.809       2.844
A3             1.4027      0.566      2.478      0.013       0.293       2.512
A4             1.1167      1.209      0.924      0.356      -1.252       3.486
A5             0.1318      1.129      0.117      0.907      -2.082       2.345
B1            -2.5334      0.657     -3.856      0.000      -3.821      -1.246
C1            -0.5577      0.412     -1.352      0.176      -1.366       0.251
C2            -0.8701      0.671     -1.296      0.195      -2.186       0.445
C3             2.1902      2.416      0.906      0.365      -2.545       6.926
D1             0.1329      0.432      0.308      0.758      -0.714       0.980
D2            -3.0153      0.625     -4.828      0.000      -4.239      -1.791
E1            -0.5656      0.417     -1.355      0.175      -1.384       0.252
E2            -0.3672      0.742     -0.495      0.621      -1.821       1.086
F1            -3.4546      1.390     -2.485      0.013      -6.180      -0.730
F2            -1.5505      1.226     -1.265      0.206      -3.953       0.852
G1_1           0.5449      0.712      0.765      0.444      -0.851       1.941
G1_2           0.7836      0.920      0.852      0.394      -1.019       2.587
==============================================================================

可以看到array這邊,我們這組同樣以p-value小於0.05,作為達到顯著性之標準。 可以發現E2(通勤成本)、G1(是否受律師協助)顯著性偏低,p-value高於0.05甚多; 而D2(是否加重職務負擔)則最為顯著、其次為B1(是否出於與職務無關動機)。

(2). 模型績效

In [24]:
# 模型績效
mae = np.mean(abs(lm.predict(X) - y))
mse = np.mean((lm.predict(X) - y) ** 2)
r_squared = lm.score(X, y)
adj_r_squared = r_squared - (1 - r_squared) * (X.shape[1] / (X.shape[0] - X.shape[1] - 1))

# 印出模型績效
print("Mean Absolute Error:", mae)
print("Mean Squared Error:", mse)
print("R squared:" ,r_squared)
print("adjusted R squared:", adj_r_squared)
Mean Absolute Error: 0.08058608058608059
Mean Squared Error: 0.08058608058608059
R squared: 0.9194139194139194
adjusted R squared: 0.914041514041514

模型績效的部分,此次呈現的為r_squared高達0.91的結果,應值信賴。

2.樣本二 六個主要變項

(1). 模型

In [25]:
from sklearn. linear_model import LinearRegression
from sklearn import linear_model
from sklearn import preprocessing, linear_model

X = df[['D2', 'B1','F1','C1','A3','A2']] #與var_y相關>0.3
y = df['var_y']  #y 注意數值或是二元
#y = df['var_y']

#lm = LinearRegression() #如果是數值y
lm = linear_model.LogisticRegression() #如果是二元y

lm.fit(X, y)
Out[25]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
In [26]:
# 項目
#print(X.columns)
# 印出係數
#print(lm.coef_)
# 印出 p-value
#print(f_regression(X, y))
# 印出截距
#print(lm.intercept_ )

#線性迴歸

#加截距項

import statsmodels.api as sm 
df_Logit=df.copy()
#print(df_Logit["intercept"])

X_Logit1 = sm.add_constant(df_Logit[['D2', 'B1','F1','C1','A3','A2']])
y_Logit1 = df_Logit['var_y'] 


from statsmodels.api import Logit
print(Logit(y_Logit1,X_Logit1).fit().summary())
Optimization terminated successfully.
         Current function value: 0.244318
         Iterations 8
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                  var_y   No. Observations:                  273
Model:                          Logit   Df Residuals:                      266
Method:                           MLE   Df Model:                            6
Date:                Tue, 15 Jun 2021   Pseudo R-squ.:                  0.6360
Time:                        08:23:41   Log-Likelihood:                -66.699
converged:                       True   LL-Null:                       -183.23
Covariance Type:            nonrobust   LLR p-value:                 1.692e-47
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const         -0.7968      0.265     -3.008      0.003      -1.316      -0.278
D2            -3.1163      0.565     -5.513      0.000      -4.224      -2.009
B1            -2.4709      0.576     -4.291      0.000      -3.599      -1.342
F1            -3.7415      0.978     -3.827      0.000      -5.657      -1.826
C1            -0.8314      0.325     -2.558      0.011      -1.469      -0.194
A3             0.8487      0.374      2.268      0.023       0.115       1.582
A2             1.4298      0.425      3.365      0.001       0.597       2.263
==============================================================================

(2). 模型績效

In [27]:
# 模型績效
mae = np.mean(abs(lm.predict(X) - y))
mse = np.mean((lm.predict(X) - y) ** 2)
r_squared = lm.score(X, y)
adj_r_squared = r_squared - (1 - r_squared) * (X.shape[1] / (X.shape[0] - X.shape[1] - 1))

# 印出模型績效
print("Mean Absolute Error:", mae)
print("Mean Squared Error:", mse)
print("R squared:" ,r_squared)
print("adjusted R squared:", adj_r_squared)
Mean Absolute Error: 0.0989010989010989
Mean Squared Error: 0.0989010989010989
R squared: 0.9010989010989011
adjusted R squared: 0.8988680492439891

爰取與var_y相關性前六高之變項,作為回歸模型之依變項,可得出除以全部變項作為依變項時外模型數值最佳之結果,可推測此六個變項為主要影響法院判斷調職合法之因素。

而此六個變項分別為:D2(是否加重職務負擔), B1(是否出於與職務無關之動機), F1(是否影響勞工作息安排), C1(是否減薪),A1(雇主是否業務緊縮、業績下滑) , A3(是否為整體性調整)

(四)決策樹

1.樣本一 全部變項

(1). 模型

In [28]:
# 切分訓練與測試資料
X = df[['A1', 'A2','A3', 'A4', 'A4', 'A5', 'B1', 'C1', 'C2', 'C3', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2','G1_1', 'G1_2']]
y = df['var_y'] 
#y = df['y2']

train_X, test_X, train_y, test_y = train_test_split(X, y, test_size = 0.3,)

#lm = LinearRegression() #如果是數值y
lm = linear_model.LogisticRegression() #如果是二元y

# 建立分類器
clf = tree.DecisionTreeClassifier(max_depth=3)
my_clf = clf.fit(train_X, train_y)
my_clf
Out[28]:
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=3, max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=None, splitter='best')
In [29]:
text_representation = tree.export_text(my_clf)
#print(text_representation)
In [30]:
np.sort(y.unique()).astype(str).tolist()


# DOT data
dot_data = tree.export_graphviz(my_clf, out_file=None, 
                feature_names=X.columns.tolist(),  
                class_names=np.sort(y.unique()).astype(str).tolist(), #也可以手輸入
                filled=True)

# Draw graph
graph = graphviz.Source(dot_data, format="png") 
graph
Out[30]:
Tree 0 D2 <= -0.5 gini = 0.479 samples = 191 value = [76, 115] class = 1 1 F1 <= 0.5 gini = 0.113 samples = 83 value = [5, 78] class = 1 0->1 True 8 B1 <= -0.5 gini = 0.45 samples = 108 value = [71, 37] class = 0 0->8 False 2 A1 <= -0.5 gini = 0.025 samples = 78 value = [1, 77] class = 1 1->2 5 E2 <= -0.5 gini = 0.32 samples = 5 value = [4, 1] class = 0 1->5 3 gini = 0.142 samples = 13 value = [1, 12] class = 1 2->3 4 gini = 0.0 samples = 65 value = [0, 65] class = 1 2->4 6 gini = 0.0 samples = 1 value = [0, 1] class = 1 5->6 7 gini = 0.0 samples = 4 value = [4, 0] class = 0 5->7 9 D1 <= -0.5 gini = 0.255 samples = 20 value = [3, 17] class = 1 8->9 12 A3 <= 0.5 gini = 0.351 samples = 88 value = [68, 20] class = 0 8->12 10 gini = 0.0 samples = 1 value = [1, 0] class = 0 9->10 11 gini = 0.188 samples = 19 value = [2, 17] class = 1 9->11 13 gini = 0.224 samples = 70 value = [61, 9] class = 0 12->13 14 gini = 0.475 samples = 18 value = [7, 11] class = 1 12->14

(2). 模型績效

In [31]:
# 預測
test_y_predicted = my_clf.predict(test_X)
print("模型預測調職合法與否結果:")
print(test_y_predicted)

# 標準答案
print("\n測試資料實際判決調職合法與否結果:")
print(test_y.values)

#混淆矩陣
from sklearn.metrics import confusion_matrix
print("\n混淆矩陣:")
confusionMatrix = confusion_matrix(test_y, test_y_predicted)
df_Matrix = pd.DataFrame(confusionMatrix, columns = ['預測結果:不合法','預測結果:合法'], index = ['實際結果:不合法','實際結果:合法'])
#print(confusionMatrix)
df_Matrix
模型預測調職合法與否結果:
[1 1 1 0 1 1 1 1 0 0 0 0 1 0 0 1 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 1 1 0 0 1
 1 1 0 1 0 0 1 1 1 1 1 0 1 1 0 1 1 1 1 0 0 1 1 1 0 1 0 0 1 0 0 0 1 1 1 1 0
 1 0 1 0 0 1 0 0]

測試資料實際判決調職合法與否結果:
[1 1 1 1 0 1 1 1 0 0 0 0 1 1 0 1 0 0 1 1 1 1 0 1 0 0 0 1 1 1 1 0 1 1 0 0 1
 1 0 0 1 0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 1 1 1 0 1 0 0 0 0 0 0 1 1 1 1 1
 1 0 1 0 0 1 1 0]

混淆矩陣:
Out[31]:
預測結果:不合法 預測結果:合法
實際結果:不合法 27 5
實際結果:合法 8 42
In [32]:
#df.columns
In [33]:
#df["C2"].value_counts()
In [34]:
# 績效
accuracy = metrics.accuracy_score(test_y, test_y_predicted)
print("預測準確率:", accuracy)
預測準確率: 0.8414634146341463

以所有變項進行決策樹之訓練,並且取其中accuracy大於0.85之組合,初步認定為對於結果影響力較大之變項。

2.樣本二 六個主要變項

(1). 模型

In [35]:
# 切分訓練與測試資料


X = df[['D2', 'B1','C1','F1','A2','A3','D1']]
y = df['var_y'] 
#y = df['y2']

train_X, test_X, train_y, test_y = train_test_split(X, y, test_size = 0.3, random_state=11)

#lm = LinearRegression() #如果是數值y
lm = linear_model.LogisticRegression() #如果是二元y

# 建立分類器
clf = tree.DecisionTreeClassifier(max_depth=3)
my_clf = clf.fit(train_X, train_y)
my_clf
Out[35]:
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=3, max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=None, splitter='best')
In [36]:
text_representation = tree.export_text(my_clf)
#print(text_representation)
In [37]:
np.sort(y.unique()).astype(str).tolist()


# DOT data
dot_data = tree.export_graphviz(my_clf, out_file=None, 
                feature_names=X.columns.tolist(),  
                class_names=np.sort(y.unique()).astype(str).tolist(), #也可以手輸入
                filled=True)

# Draw graph
graph = graphviz.Source(dot_data, format="png") 
graph
Out[37]:
Tree 0 D2 <= -0.5 gini = 0.485 samples = 191 value = [79, 112] class = 1 1 F1 <= 0.5 gini = 0.1 samples = 76 value = [4, 72] class = 1 0->1 True 8 B1 <= -0.5 gini = 0.454 samples = 115 value = [75, 40] class = 0 0->8 False 2 C1 <= 0.5 gini = 0.027 samples = 72 value = [1, 71] class = 1 1->2 5 B1 <= -0.5 gini = 0.375 samples = 4 value = [3, 1] class = 0 1->5 3 gini = 0.0 samples = 56 value = [0, 56] class = 1 2->3 4 gini = 0.117 samples = 16 value = [1, 15] class = 1 2->4 6 gini = 0.0 samples = 1 value = [0, 1] class = 1 5->6 7 gini = 0.0 samples = 3 value = [3, 0] class = 0 5->7 9 C1 <= 0.5 gini = 0.227 samples = 23 value = [3, 20] class = 1 8->9 12 A2 <= 0.5 gini = 0.34 samples = 92 value = [72, 20] class = 0 8->12 10 gini = 0.111 samples = 17 value = [1, 16] class = 1 9->10 11 gini = 0.444 samples = 6 value = [2, 4] class = 1 9->11 13 gini = 0.269 samples = 81 value = [68, 13] class = 0 12->13 14 gini = 0.463 samples = 11 value = [4, 7] class = 1 12->14

(2). 模型績效

In [38]:
# 預測
test_y_predicted = my_clf.predict(test_X)
print("模型預測調職合法與否結果:")
print(test_y_predicted)

# 標準答案
print("\n測試資料實際判決調職合法與否結果:")
print(test_y.values)

#混淆矩陣
print("\n混淆矩陣:")
confusionMatrix = confusion_matrix(test_y, test_y_predicted)
df_Matrix = pd.DataFrame(confusionMatrix, columns = ['預測結果:不合法','預測結果:合法'], index = ['實際結果:不合法','實際結果:合法'])
#print(confusionMatrix)
df_Matrix
模型預測調職合法與否結果:
[1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1 1 0
 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 1 0 0 1 1 0 1 1 0 1
 0 1 1 1 1 1 1 1]

測試資料實際判決調職合法與否結果:
[1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 1 0 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0
 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 0 1 1 0 1 0 0 1 0 1 1 1 0 1 1 0 0
 0 1 1 1 1 1 1 1]

混淆矩陣:
Out[38]:
預測結果:不合法 預測結果:合法
實際結果:不合法 23 6
實際結果:合法 3 50
In [39]:
# 績效
accuracy = metrics.accuracy_score(test_y, test_y_predicted)
print("預測準確率:", accuracy)
預測準確率: 0.8902439024390244

(3). 特徵重要性

In [40]:
# get importance
importance = clf.feature_importances_
#importance
In [41]:
# Import matplotlib for plotting and use magic command for Jupyter Notebooks
import matplotlib.pyplot as plt
%matplotlib inline
# Set the style
plt.style.use('fivethirtyeight')
# list of x locations for plotting
x_values = list(range(len(importance)))
# Make a bar chart
plt.bar(x_values, importance)
# Tick labels for x axis
plt.xticks(x_values, X)
# Axis labels and title
plt.ylabel('Importance'); plt.xlabel('Variable'); plt.title('Variable Importances');

自圖中可知,D2為最好之分類標準。在最上方的節點中,總共有191筆測試資料,其中112筆為「調職合法」(class=1)、另外79筆為「調職不合法」。

第一列

在第一列中,僅有D2此一變項。若D2越偏向-1,則代表「並未加重職務負擔」;反之,偏向+1則代表「加重職務負擔」。在「並未加重職務負擔」為真之情形下,共有76筆資料,其中72筆調職合法;而「加重職務負擔」為真之情形,115筆資料中則僅有40筆調職合法。

第二列

在第二列中,則有F1(是否不利勞工作息安排)、B1(是否出於與職務無關動機)兩變項。先觀察F1的節點。在「未不利勞工作息安排」為真之情形下,共有72筆資料,其中71筆調職合法;「不利勞工作息安排」為真之情形下,共有4筆資料,其中1筆調職合法。後觀察B1的節點。在「非出於與職務無關之動機」為真之情形下,共有23筆資料,其中20筆調職合法;「出於與職務無關之動機」為真之情形下,共有92筆資料,其中20筆調職合法。

第三列

在第三列中,則有C1(工資是否減少)、B1(是否出於與職務無關動機)、A2(勞工工作表現是否不佳)三變項。先觀左方C1的節點。在「工資未減少」為真之情形下,共有56筆資料,全部皆為調職合法;「工資減少」為真之情形下,共有16筆資料,其中仍有15筆調職合法。後觀察B1的節點。在「非出於與職務無關之動機」為真之情形下,共有1筆資料,調職合法;「出於與職務無關之動機」為真之情形下,共有3筆資料,調職皆不合法。再觀察右方C1的節點。在「工資未減少」為真之情形下,共有17筆資料,其中16筆調職合法;「工資減少」為真之情形下,共有6筆資料,其中仍有4筆調職合法。最後觀察A2的節點。在「勞工工作表現未有不佳」為真之情形下,共有81筆資料,其中僅13筆為調職合法;「勞工工作表現不佳」為真之情形下,共有11筆資料,其中7筆調職合法。

小結

依照上開結果,至少可歸納出八項結論:

  1. 在未加重職務負擔、未不利勞工作息安排且工資未減少之情形,調職合法。
  2. 在未加重職務負擔、未不利勞工作息安排但工資減少之情形,調職仍有很大機率合法。
  3. 在未加重職務負擔、不利勞工作息安排但非出於與職務無關之動機之情形,調職應為合法(樣本數僅有1)。
  4. 在未加重職務負擔、不利勞工作息安排且出於與職務無關之動機之情形,調職應為非法(樣本數僅有3)。
  5. 在加重職務負擔、但非出於與職務無關之動機且工資未減少之情形,調職有很大機率合法。
  6. 在加重職務負擔、非出於與職務無關之動機但工資減少之情形,調職傾向於合法。
  7. 在加重職務負擔、出於與職務無關之動機且勞工表現並未不佳之情形,調職傾向於非法。
  8. 在加重職務負擔、出於與職務無關之動機且勞工表現不佳之情形,調職傾向於合法。

(五)隨機森林

In [42]:
# Use numpy to convert to arrays
import numpy as np
In [43]:
# Using Skicit-learn to split data into training and testing sets
from sklearn.model_selection import train_test_split

X = df[['A1', 'A2','A3', 'A4', 'A4', 'A5', 'B1', 'C1', 'C2', 'C3', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2', 'G1_1','G1_2']]
y = df['var_y'] 
# Split the data into training and testing sets
train_features, test_features, train_labels, test_labels = train_test_split(X , y, test_size = 0.25, random_state = 42)

由於本組在十四個變項中挑選其中六個進行決策樹的訓練,可能會有overfitting的問題,造成此訓練結果僅能較好的應用於目前所蒐集的判決,而不一定能一體適用於往後的判決;因此再利用隨機森林跑全部的變項,希望能藉此再次確認挑選出的六組變項是否確實在實務的判斷上,具有重要性。

In [44]:
print('Training Features Shape:', train_features.shape)
print('Training Labels Shape:', train_labels.shape)
print('Testing Features Shape:', test_features.shape)
print('Testing Labels Shape:', test_labels.shape)
Training Features Shape: (204, 18)
Training Labels Shape: (204,)
Testing Features Shape: (69, 18)
Testing Labels Shape: (69,)
In [45]:
# Import the model we are using
from sklearn.ensemble import RandomForestRegressor
# Instantiate model with 1000 decision trees
rf = RandomForestRegressor(n_estimators = 1000, random_state = 42)
# Train the model on training data
rf.fit(train_features, train_labels);
In [46]:
# Use the forest's predict method on the test data
predictions = rf.predict(test_features)
# Calculate the absolute errors
errors = abs(predictions - test_labels)
# Print out the mean absolute error (mae)
print('Mean Absolute Error:', round(np.mean(errors), 2), 'degrees.')
Mean Absolute Error: 0.16 degrees.
In [47]:
# Get numerical feature importances
importances = list(rf.feature_importances_)
# List of tuples with variable and importance
feature_importances = [(feature, round(importance, 2)) for feature, importance in zip(X, importances)]
# Sort the feature importances by most important first
feature_importances = sorted(feature_importances, key = lambda x: x[1], reverse = True)
# Print out the feature and importances 
[print('Variable: {:20} Importance: {}'.format(*pair)) for pair in feature_importances];
Variable: D2                   Importance: 0.42
Variable: B1                   Importance: 0.09
Variable: F1                   Importance: 0.08
Variable: A2                   Importance: 0.07
Variable: A3                   Importance: 0.07
Variable: C1                   Importance: 0.05
Variable: E1                   Importance: 0.05
Variable: D1                   Importance: 0.04
Variable: A1                   Importance: 0.03
Variable: A5                   Importance: 0.02
Variable: C2                   Importance: 0.02
Variable: A4                   Importance: 0.01
Variable: A4                   Importance: 0.01
Variable: E2                   Importance: 0.01
Variable: G1_1                 Importance: 0.01
Variable: G1_2                 Importance: 0.01
Variable: C3                   Importance: 0.0
Variable: F2                   Importance: 0.0
In [48]:
# Import matplotlib for plotting and use magic command for Jupyter Notebooks
import matplotlib.pyplot as plt
%matplotlib inline
# Set the style
plt.style.use('fivethirtyeight')
# list of x locations for plotting
x_values = list(range(len(importances)))
# Make a bar chart
plt.bar(x_values, importances)
# Tick labels for x axis
plt.xticks(x_values, X)
# Axis labels and title
plt.ylabel('Importance'); plt.xlabel('Variable'); plt.title('Variable Importances');

從上圖中可知,使用隨機森林訓練模型,D2仍為所有變項中最為重要者;而F1、B1、A2、A3的重要性雖然遠不如D2顯著,但亦為前述六組變項中,具有決策參考價值者。因此似乎可以如此做結:加重職務負擔與否,為調職是否合法最重要的判斷因素。

七、結論

從以與var_y相關性前六高之變項即可產出數據次優(最優者為全部變項)的回歸模型,可得出此六項因素為法院判斷時最具影響力之因素。而從決策樹、隨機森林之結果又可得知其中「是否加重職務負擔」為法院最先考量且最關鍵之因素,而可作為調職合法性訴訟之攻防重點。

而除了找出重要性較高的因素,本文亦就部份變項未出現預期影響力時,嘗試推敲原因,例如:減薪若加上勞工表現不佳、雇主整體性調整,可能被認為此時無不利之變更,而判決調職合法;通勤成本雖有一定程度增加,但仍可能被法院認為是在「依社會通念容忍程度內」而被判定為合法。

最後,本組在課程發表中蒙其他修課同學建議,因調職類型之不同,部分變項並不會出現於法院之考量(如:非屬有距離調動之調職,法院即不會考量通勤成本之增加)。若更細膩區分特定類型之調職,似乎更能精確各變項對法院判斷之影響力,於此存在改善空間,未來若欲繼續研究可朝此方向進行調整。

In [49]:
#隱藏code
from IPython.display import HTML

HTML('''<script>
code_show=true; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>''')
Out[49]: