Go 應用於資料科學的案例分享:付多少小費
Go 應用於資料科學的案例分享:付多少小費
- 原文地址:https://www.ardanlabs.com/blog/2021/07/go-data-science-how-much-tip.html
- 原文作者:Miki Tebeka
- 本文永久連結:https://github.com/gocn/translator/blob/master/2021/w30_Data_Science_in_Go_How_Much_To_Tip.md
- 譯者:lsj1342
- 校對:laxiaohong、cvley
提出問題
當處理資料科學難題時,你總會以一個你想要回答的問題開始。這個問題將會影響你選擇資料,探索過程以及解釋結果。
本文的問題是:你應該給計程車司機多少(按百分比)小費?
為了回答這個問題,我們將使用紐約市計程車資料集的一部分,使用的資料檔案是taxi-01-2020-sample.csv.bz2
注意: CSV 是一種十分令人討厭的格式。它沒有標準,沒有模式,所有內容都被解釋為文字(與 JSON 不同)。如果可以,請選擇其他格式。我首選的資料儲存格式是 SQLite 。
探索過程的程式碼
我們正在尋找問題的答案,我們將專注於快速實現。如果以後將此程式碼投入到生產環境下,那麼繼續重構它。
為了簡化輸入的工作,我們將在標準輸入中傳遞輸入檔案。我們將有幾個探索資料的階段,每個階段都有一個相應的命令列開關。在 main
函式中,我們有以下行:
r := bzip2.NewReader(os.Stdin)
並且,我們在每個探索步驟都會呼叫到 r
。
初探
在開始處理資料之前,快速檢視它是否符合你的期望。此外,你還應該檢查資料是否適合放入記憶體。
步驟 1:初次檢視
19 func firstLook(r io.Reader) error {
20 var numLines, numBytes int
21 s := bufio.NewScanner(r)
22 for s.Scan() {
23 if numLines < 5 {
24 fmt.Println(s.Text())
25 }
26 numBytes += len(s.Text())
27 numLines++
28 }
29
30 if err := s.Err(); err != nil {
31 return err
32 }
33
34 fmt.Printf("size: %.2fMB\n", float64(numBytes)/1_000_000)
35 fmt.Printf("lines: %d\n", numLines)
36 return nil
37 }
步驟 1 顯示了對資料的初步瞭解。在第 21 行,我們建立了一個 bufio.Scanner 用以逐行掃描。在第 23-25 行,我們列印檔案的前 5 行。在第 34 行,我們列印檔案大小,在第 35 行,我們列印了行數。
步驟 2: 執行程式碼
$ go run taxi.go -first_look < taxi-01-2020-sample.csv.bz2
VendorID,tpep_pickup_datetime,tpep_dropoff_datetime,passenger_count,trip_distance,RatecodeID,store_and_fwd_flag,PULocationID,DOLocationID,payment_type,fare_amount,extra,mta_tax,tip_amount,tolls_amount,improvement_surcharge,total_amount,congestion_surcharge
2,2003-01-01 00:07:17,2003-01-01 14:16:59,1.0,0.0,1.0,N,193,193,2.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
2,2008-12-31 23:35:00,2008-12-31 23:36:53,1.0,0.42,1.0,N,263,263,2.0,3.5,0.5,0.5,0.0,0.0,0.3,7.3,2.5
2,2009-01-01 00:06:19,2009-01-01 00:10:22,1.0,0.85,1.0,N,107,137,2.0,5.0,0.0,0.5,0.0,0.0,0.3,8.3,2.5
2,2009-01-01 00:48:28,2009-01-01 00:57:48,1.0,0.93,1.0,N,100,186,2.0,7.5,0.0,0.5,0.0,0.0,0.3,10.8,2.5
size: 101.68MB
lines: 1000001
步驟 2 展示瞭如何執行第一步。我們用 go run
來執行程式碼。在輸出中,我們看到檔案的前 5 行以及未壓縮檔案的大小和行數。
該檔案是一個 CSV 檔案,小到可以放入記憶體。要計算小費百分比,我們只需要兩列:tip_amount
和 total_amount
。如果您對資料模式感到好奇,請參閱此處。
載入資料
一旦初次檢視的結果與您的假設一致,您就可以載入資料。我們將使用 github.com/jszwec/csvutil
解析 CSV 和 gonum 來計算一些統計資訊。
步驟 3: 依賴引入
14 "github.com/jszwec/csvutil"
15 "gonum.org/v1/gonum/floats"
16 "gonum.org/v1/gonum/stat"
步驟 3 展示了程式碼中的外部依賴匯入。在第 14 行,我們匯入 csvutil
,在第 15-16 行,我們從gonum
匯入 floats
和 stat
。
步驟 4: 載入資料
62 type Row struct {
63 Tip float64 `csv:"tip_amount"`
64 Total float64 `csv:"total_amount"`
65 }
66
67 func loadData(r io.Reader) ([]float64, []float64, error) {
68 var tip, total []float64
69 dec, err := csvutil.NewDecoder(csv.NewReader(r))
70 if err != nil {
71 return nil, nil, err
72 }
73
74 for {
75 var row Row
76 err := dec.Decode(&row)
77
78 if err == io.EOF {
79 break
80 }
81
82 if err != nil {
83 return nil, nil, err
84 }
85
86 tip = append(tip, row.Tip)
87 total = append(total, row.Total)
88 }
89
90 return tip, total, nil
91 }
步驟 4 顯示了我們如何載入資料。在第 62-65 行,我們定義了一個 Row
結構體來包含我們感興趣的欄位。在第 68 行,我們定義了 tip
和 amount
切片來儲存 CSV 中 tip_amount
和 total_amount
欄位的值。在第 74-88 行,我們執行一個 for 迴圈來上傳資料。最後在第 90 行,進行資料返回。
步驟 5: 統計
39 func statistics(r io.Reader) error {
40 tip, total, err := loadData(r)
41 if err != nil {
42 return err
43 }
44
45 fmt.Printf(
46 "tip: min=%.2f, mean=%.2f, max=%.2f\n",
47 floats.Min(tip),
48 stat.Mean(tip, nil),
49 floats.Max(tip),
50 )
51
52 fmt.Printf(
53 "total: min=%.2f, mean=%.2f, max=%.2f\n",
54 floats.Min(total),
55 stat.Mean(total, nil),
56 floats.Max(total),
57 )
58
59 return nil
60 }
步驟 5 顯示了 statistics 我們資料探索的步驟。在第 40 行,我們載入日期。在第 45-50 行,我們列印了小費的最小值、平均值(平均值)和最大值。在第 52-57 行,我們對總數執行相同的操作。
步驟 6: 執行統計程式碼
$ go run taxi.go -stats < taxi-01-2020-sample.csv.bz2
tip: min=-11.80, mean=2.21, max=333.50
total: min=-333.30, mean=18.47, max=4268.30
步驟 6 顯示瞭如何執行統計步驟的程式碼。我們可以看到有一些不好的值。兩個最小值都是負數並且總金額的最大值超過 4,000 美元。
在任何現實生活中的資料集中,都會有錯誤的值,你需要決定如何處理它們。我們將採用簡單的方法並忽略它們。我們將過濾掉負值。此外,由於我們不打算乘坐費用超過 100 美元的計程車,因此我們將過濾掉 total_amount
大於 100 的行。
小費計算
步驟 7: 載入過濾資料
114 func loadDataFiltered(r io.Reader) ([]float64, []float64, error) {
115 var tip, total []float64
116 dec, err := csvutil.NewDecoder(csv.NewReader(r))
117 if err != nil {
118 return nil, nil, err
119 }
120
121 for {
122 var row Row
123
124 err := dec.Decode(&row)
125 if err == io.EOF {
126 break
127 }
128
129 if err != nil {
130 return nil, nil, err
131 }
132
133 if row.Total <= 0 || row.Tip <= 0 || row.Total > 100 {
134 continue
135 }
136
137 tip = append(tip, row.Tip)
138 total = append(total, row.Total)
139 }
140
141 return tip, total, nil
142 }
步驟 7 展示了對過濾資料的載入。和 loadData
唯一的區別是第 133-135 行的過濾操作。
現在我們可以計算我們想要支付的小費。我們希望保持慷慨,因此我們將使用 75% 的分位數值。75% 分位數(或百分位數)是 75% 的值低於它的數字。
步驟 8: 期待支出的小費
93 func desiredTip(r io.Reader) error {
94 tip, total, err := loadDataFiltered(r)
95 if err != nil {
96 return err
97 }
98
99 fmt.Printf("%d filtered values\n", len(tip))
100
101 pct := make([]float64, len(tip))
102 for i, t := range tip {
103 pct[i] = t / (total[i] - t)
104 }
105
106 // stat.Quantile required sorted values
107 sort.Float64s(pct)
108 q := 0.75
109 val := stat.Quantile(q, stat.Empirical, pct, nil)
110 fmt.Printf("%.2f quantile tip: %.2f\n", q, val)
111 return nil
112 }
步驟 8 展示了 desiredTip
函式。在第 94 行,我們載入了過濾後的資料。在第 99 行,我們列印了過濾後的行數,這樣方便我們檢查不會過濾掉太多行。在第 101-104 行,我們建立了一個百分比切片。最後在第 107-110 行,我們計算 75% 的百分位數,並在第 110 行,我們把它列印了出來。
步驟 9: 執行程式碼
$ go run taxi.go -tip < taxi-01-2020-sample.csv.bz2
716422 filtered values
0.75 quantile tip: 0.20
步驟 9 展示了 tip
步驟輸出。我們看到我們過濾掉了大約 30% 的行。最後,我們看到 75% 的分位數是 20%。
可是,等等!也許我們會在週末多給點小費?我們來看一下:
步驟 10: 載入攜帶時間的資料
145 func unmarshalTime(data []byte, t *time.Time) error {
146 var err error
147 *t, err = time.Parse("2006-01-02 15:04:05", string(data))
148 return err
149 }
150
151 type TimeRow struct {
152 Tip float64 `csv:"tip_amount"`
153 Total float64 `csv:"total_amount"`
154 Time time.Time `csv:"tpep_pickup_datetime"`
155 }
156
157 func loadDataWithTime(r io.Reader) ([]time.Time, []float64, []float64, error) {
158 var tip, total []float64
159 var times []time.Time
160 dec, err := csvutil.NewDecoder(csv.NewReader(r))
161 dec.Register(unmarshalTime)
162 if err != nil {
163 return nil, nil, nil, err
164 }
165
166 for {
167 var row TimeRow
168
169 err := dec.Decode(&row)
170 if err == io.EOF {
171 break
172 }
173
174 if err != nil {
175 return nil, nil, nil, err
176 }
177
178 if row.Total <= 0 || row.Tip <= 0 || row.Total > 100 {
179 continue
180 }
181
182 tip = append(tip, row.Tip)
183 total = append(total, row.Total)
184 times = append(times, row.Time)
185 }
186
187 return times, tip, total, nil
188 }
步驟 10 顯示瞭如何載入資料的時間維度。在第 145-149 行,我們編寫了一個 unmarshalTime
函式來從 []byte
解析為時間。在第 151-155 行,我們定義 TimeRow
為包含 Time
欄位的行。在第 159 行,我們定義了 times
切片,在第 160 行,我們註冊 unmarshalTime
以處理 time.Time
欄位。最後在第 187 行,我們返回時間、小費和總數。
步驟 11: 按工作日計算小費
190 func weekdayTip(r io.Reader) error {
191 times, tip, total, err := loadDataWithTime(r)
192 if err != nil {
193 return err
194 }
195
196 pct := make(map[time.Weekday][]float64)
197 for i, t := range tip {
198 wday := times[i].Weekday()
199 p := t / (total[i] - t)
200 pct[wday] = append(pct[wday], p)
201 }
202
203 for wday := time.Sunday; wday < time.Saturday; wday += 1 {
204 // stat.Quantile required sorted values
205 p := pct[wday]
206 sort.Float64s(p)
207 q := 0.75
208 val := stat.Quantile(q, stat.Empirical, p, nil)
209 fmt.Printf("%-10s: %.2f quantile tip: %.2f (%6d samples)\n", wday, q, val, len(p))
210 }
211
212 return nil
213 }
步驟 11 展示了 “工作日小費” 計算。在第 196 行,我們使用字典來儲存每個工作日的百分比。在第 197 到 201 行,我們填充每個工作日的百分比,這相當於資料庫中的 “GROUP BY” 操作。在第 203-209 行,我們遍歷每個工作日,計算 0.75 分位數並將其列印出來。
在第 209 行,我們使用 -10s%
讓所有工作日至少佔 10 個字元來行對齊。這似乎是一個微不足道的細節,但對齊輸出對於我們來說會更容易比較 - 正如您在下面的輸出中看到的那樣。出於同樣的原因,我們也對齊了樣本數量。
步驟 12: 執行程式碼
$ go run taxi.go -daily < taxi-01-2020-sample.csv.bz2
Sunday : 0.75 quantile tip: 0.20 ( 77942 samples)
Monday : 0.75 quantile tip: 0.20 ( 82561 samples)
Tuesday : 0.75 quantile tip: 0.20 ( 97634 samples)
Wednesday : 0.75 quantile tip: 0.20 (118497 samples)
Thursday : 0.75 quantile tip: 0.20 (125692 samples)
Friday : 0.75 quantile tip: 0.20 (125743 samples)
步驟 12 執行了上步驟的程式碼,展示了每天的資料結果。我們可以看到週末小費的百分比並沒有差異。
結論
只需要一點好奇心並會一點 Go 就可以讓您在資料科學之旅中走得更遠。您不必使用深度學習、決策樹、支援向量機和其他演算法來獲得有用的答案。
您正如何將 Go 用於資料科學?我很想聽聽,請通過 miki@ardanlabs.com 聯絡我。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 科普文:銀行業9大資料科學應用案例解析!行業大資料資料科學
- Python資料科學(三) python與資料科學應用(Ⅲ)Python資料科學
- Python資料科學(二) python與資料科學應用(Ⅱ)Python資料科學
- Python資料科學(一) python與資料科學應用(Ⅰ)Python資料科學
- python應用資料科學的優勢Python資料科學
- 基於vue開發的線上付費課程應用Vue
- 敏捷資料科學:用Hadoop建立資料分析應用敏捷資料科學Hadoop
- 【應用場景】AISWare AIDB 亞信資料庫在多省份計費系統應用案例AI資料庫
- 學習資料科學的五大免費資源資料科學
- (資料科學學習手札119)Python+Dash快速web應用開發——多頁面應用資料科學PythonWeb
- Orchest是用於資料科學的基於瀏覽器的IDE資料科學瀏覽器IDE
- 大資料應用案例大資料
- 3 個用於資料科學的頂級 Python 庫資料科學Python
- 理工大學大資料應用的三個學科大資料
- 16個用於資料科學和機器學習的頂級平臺資料科學機器學習
- 人的資料科學與機器資料科學資料科學
- CIKM 2016:大資料科學的前沿與應用大資料資料科學
- Oracle資料庫學習應用:經驗分享Oracle資料庫
- AI實戰分享 | 基於CANN的輔助駕駛應用案例AI
- 關於資料科學家,我們應該知道的這些事資料科學
- 一次競賽案例的分享——基於正規表示式的深度學習應用深度學習
- [譯] 用行為經濟學來傳達付費應用訂閱的價值
- 關於資料科學的十本好書資料科學
- Java可以用於機器學習和資料科學嗎? - kdnuggetsJava機器學習資料科學
- Python機器學習 5個資料科學家案例解析Python機器學習資料科學
- 付費學習之路
- 讓科學重回資料科學資料科學
- 常用構建資料科學應用程式的七個Python庫資料科學Python
- 關於知識付費的思考
- 資料科學資料科學
- 科學研究與大資料概念的濫用大資料
- 用 Python 入門資料科學Python資料科學
- 資料科學的原理與技巧 一、資料科學的生命週期資料科學
- 基於技能的改善資料科學實踐的方法資料科學
- 澳門美團跨多應用測試程式碼一鍵生成案例分享
- 基於 Kyma 的企業級雲原生應用的擴充套件案例分享套件
- PostgreSQLHybridDBforPG毫秒級多維資料透視案例分享SQL
- MapReduce應用案例--簡單的資料去重