第 2 章 CSV 檔案
第 2 章 CSV 檔案
2。1 基礎 Python 與 pandas
2。1。1 讀寫 CSV 檔案(第 1 部分)
基礎 Python,不使用 CSV 模組
如果不使用 python 的 csv 模組,那麼如何讀寫 csv 檔案?可參考以下程式碼:
import
sys
input_file
=
sys
。
argv
[
1
]
#獲取輸入的檔案
output_file
=
sys
。
argv
[
2
]
#獲取輸出的檔案
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
filereader
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
filewriter
:
header
=
filereader
。
readline
()
header
=
header
。
strip
()
header_list
=
header
。
split
(
‘,’
)
(
header_list
)
filewriter
。
write
(
‘,’
。
join
(
map
(
str
,
header_list
))
+
‘
\n
’
)
for
row
in
filereader
:
row
=
row
。
strip
()
row_list
=
row
。
split
(
‘,’
)
(
row_list
)
filewriter
。
write
(
‘,’
。
join
(
map
(
str
,
row_list
))
+
‘
\n
’
)
對於以上程式碼中的
sys。argv[]
,可參考 覆手為雲的介紹,弄明白什麼是 argv[] 後,再參考書中 ${P}
{53}-{P}
{54}$ 的操作方法,Mac 上的具體實現步驟如下:
開啟終端;
cd 命令到本檔案所儲存的檔案路徑:
cd /Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料
;
在終端輸入此命令:
python3 1csv_read_with_simple_parsing_and_write。py supplier_data。csv 1output。csv
,其中
python3
指的是讓終端執行 python3 的命令,而
1csv_read_with_simple_parsing_and_write。py
就是要執行的 python 命令,也就是書中的程式碼,
supplier_data。csv
是向這個命令提交的第一個引數,也就是程式碼中指代的
sys。argv[1]
,同理可得
1output。csv
是向程式碼提交的第二個引數,即
sys。argv[2]
最後呈上終端執行時的 gif 圖:
利用 Pandas 也可以處理 CSV 檔案,具體參考如下程式碼:
import
sys
import
pandas
as
pd
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
data_frame
=
pd
。
read_csv
(
input_file
)
(
data_frame
)
data_frame
。
to_csv
(
output_file
,
index
=
False
)
在實際執行時,我的終端總是提示我找不到 pandas_,發現是無法正確找到 pandas 這個庫,所以我稍微修改了一下原始碼,如下:
import
sys
import
pandas
as
pd
input_file
=
‘/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data。csv’
output_file
=
‘/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/1output。csv’
data_frame
=
pd
。
read_csv
(
input_file
)
(
data_frame
)
data_frame
。
to_csv
(
output_file
,
index
=
False
)
請注意,以上程式碼的 input_file 和 output_file 要置換為你自己電腦上相應檔案的路徑,否則無法執行。
再來看看上面的程式碼在 CodeRunner 中的執行結果:
2。1。2 基本字串分析是如何失敗的
對於 1csv_read_with_simple_parsing_and_write。py 中的程式碼,要考慮一種情況,就是如果資料中有逗號怎麼辦,程式碼是以逗號分隔每行資料中的每個資料,如果資料本身有逗號,就會形成干擾。
2。1。3 讀寫 CSV 檔案(第 2 部分)
基礎 Python,使用 CSV 模組
使用 CSV 模組的一個好處就是:不需要僅僅為了正確處理資料而花費時間來設計正則表示式和條件邏輯。
將 supplier_data。csv 中 cost 一列的最下方兩個資料更改為 6,015。00 和 1,006,015。00,然後新建一個 。py 檔案,命名為 2csv_reader_parsing_and_write。py,儲存在對應的資料夾下,程式碼如下:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
,
delimiter
=
‘,’
)
filewriter
=
csv
。
writer
(
csv_out_file
,
delimiter
=
‘,’
)
for
row_list
in
filereader
:
(
row_list
)
filewriter
。
writerow
(
row_list
)
執行結果如下:
2。2 篩選特定的行
注意以下虛擬碼的結構
:
for
row
in
filereader
:
***
if
value
in
row
meets
some
business
rule
or
set
of
rules
:
***
do
something
else
:
do
something
else
2。2。1 行中的值滿足某個條件
基礎 Python
以下程式碼可檢驗行值是否滿足兩個具體條件,並將滿足條件的行的子集寫入一個輸出檔案:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header
=
next
(
filereader
)
#使用 csv 模組的 next 函式讀出輸入檔案的第一行,賦名 header 列表
filewriter
。
writerow
(
header
)
for
row_list
in
filereader
:
#按行讀取資料
supplier
=
str
(
row_list
[
0
])
。
strip
()
cost
=
str
(
row_list
[
3
])
。
strip
(
‘$’
)
。
replace
(
‘,’
,
‘’
)
if
supplier
==
‘Supplier Z’
or
float
(
cost
)
>
600。0
:
filewriter
。
writerow
(
row_list
)
有了上述的程式碼,我們可以稍微修改一下,在 Kaggle 官網上找到 YouTube 的一些影片觀看資料來進行簡單的篩選,我已經將要讀取的名為 YouTubeReadFile。csv 的檔案放在倉庫中,具體程式碼如下:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
#要讀取的 csv 檔名為 YouTubeReadFile。csv
output_file
=
sys
。
argv
[
2
]
#要寫入的 csv 檔名為 YouTubeWriteFile。csv
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header
=
next
(
filereader
)
filewriter
。
writerow
(
header
)
for
row_list
in
filereader
:
views
=
int
(
str
(
row_list
[
7
])
。
strip
())
# 觀看人數
likes
=
int
(
str
(
row_list
[
8
])
。
strip
())
# 點贊人數
if
views
>=
1147000
and
likes
>=
39000
:
# 篩選觀看人數和點贊人數均大於平均數的資料,共 5994 個
filewriter
。
writerow
(
row_list
)
利用 pandas 選擇符合特定條件值的行
loc 函式:pandas 提供的可以同時選擇特定行與列的函式。在逗號前面設定行篩選條件,在逗號後面設定列篩選條件。
如果我想在 supplier_data。csv 中篩選供應商名稱包含字母‘Z’,或者 cost 大於 600。0 的資料應該如何做呢?且看具體示例程式碼 pandas_value_meets_condition。py:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
data_frame
=
pd
。
read_csv
(
input_file
)
#讀取輸入的表格
data_frame
[
‘Cost’
]
=
data_frame
[
‘Cost’
]
。
str
。
strip
(
‘$’
)
# 試試看,如果某個 Cost 的值有逗號會怎樣,比如 6,015。00
data_frame
[
‘Cost’
]
=
data_frame
[
‘Cost’
]
。
str
。
replace
(
‘,’
,
‘’
)
。
astype
(
float
)
data_frame_value_meets_condition
=
data_frame
。
loc
[(
data_frame
[
‘Supplier Name’
]
。
str
。
contains
(
‘Z’
))
|
(
data_frame
[
‘Cost’
]
>
600。0
),:]
data_frame_value_meets_condition
。
to_csv
(
output_file
,
index
=
False
)
原書的程式碼與本程式碼有些不一樣,原書沒有考慮到 Cost 數值中有逗號的情形,這在轉換為 float 時會報錯。
同樣參考以上程式碼,如果我想篩選出 YouTubeFile。csv 中 views 大於 114700,likes 大於 39000 且 comment_count 大於 5043 的電視節目,應該怎樣做呢?可以參考下面的這段程式碼:
import
sys
import
pandas
as
pd
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
data_frame
=
pd
。
read_csv
(
input_file
)
#讀取輸入的 csv 檔案,此處為 YouTubeReadFile。csv
data_frame
[
‘views’
]
=
data_frame
[
‘views’
]
。
astype
(
float
)
data_frame
[
‘likes’
]
=
data_frame
[
‘likes’
]
。
astype
(
float
)
data_frame
[
‘comment_count’
]
=
data_frame
[
‘comment_count’
]
。
astype
(
float
)
data_frame_value_meets_condition
=
data_frame
。
loc
[(
data_frame
[
‘views’
]
>=
1147000
)
&
(
data_frame
[
‘likes’
]
>=
39000
)
&
(
data_frame
[
‘comment_count’
]
>=
5043
),:]
data_frame_value_meets_condition
。
to_csv
(
output_file
,
index
=
False
)
這裡會將 YouTubeReadFile。csv 檔案中符合條件的值篩選出來,並存儲到 YouTube_pandas_value_meets_condition。py 中。
2。2。2 行中的值屬於某個集合
如果行中的某個值屬於某個範圍,也可以篩選出來,比如特定的某幾個日期,再比如特定的某幾種屬性,可參考以下程式碼:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
#此處獲取的是 supplier_data。csv
output_file
=
sys
。
argv
[
2
]
#此處輸出的是 4output。csv
important_dates
=
[
‘1/20/14’
,
‘1/30/14’
]
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header
=
next
(
filereader
)
filewriter
。
writerow
(
header
)
for
row_list
in
filereader
:
a_date
=
row_list
[
4
]
if
a_date
in
important_dates
:
filewriter
。
writerow
(
row_list
)
以上程式碼是透過 csv 庫實現的效果,如果用 pandas 會更加簡單,如下所示:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
data_frame
=
pd
。
read_csv
(
input_file
)
important_dates
=
[
‘1/20/14’
,
‘1/30/14’
]
data_frame_value_in_set
=
data_frame
。
loc
[
data_frame
[
‘Purchase Date’
]
。
isin
(
important_dates
),:]
#isin 這個命令很簡單實用
data_frame_value_in_set
。
to_csv
(
output_file
,
index
=
False
)
2。2。3 行中的值匹配於某個模式/正則表示式
正則表示式一般用於查詢某種通用規律的資料,例如:
身份證號開頭是 110102 的人,這代表某人的籍貫是北京;
學號第二位到第五位是 2018 的學生,通常這可能意味著他是 20 年入學,而 18 代表學院編號;
在某個城市,車牌尾號的數字是 1,3,5 的汽車在週一,週三,週五限行。
同樣的例子還有很多,可以透過給出的資料進行篩選,下面看一段書中用 csv 和 re 兩個庫實現的程式碼:
import
csv
import
re
import
sys
input_file
=
sys
。
argv
[
1
]
#此處為 supplier_data。csv
output_file
=
sys
。
argv
[
2
]]
#此處為 5csv_reader_value_matches_pattern。csv
pattern
=
re
。
compile
(
r
‘(?P
,
re
。
I
)
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header
=
next
(
filereader
)
filewriter
。
writerow
(
header
)
for
row_list
in
filereader
:
invoice_number
=
row_list
[
1
]
if
pattern
。
search
(
invoice_number
):
filewriter
。
writerow
(
row_list
)
如何執行?此處只是補充說明,實際上在之前的筆記中有過講解,只要在終端用 cd 命令先導航到此 。py 檔案的路徑下,比如在我的 Mac 上就是
cd /Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料
然後執行此命令
python3 5csv_reader_value_matches_pattern。py supplier_data。csv 5csv_reader_value_matches_pattern。csv
即可將 supplier_data。csv 中符合要求的資料寫入 5csv_reader_value_matches_pattern。csv 中。
在以上程式碼中,我們要搜尋的是以 “001-”開頭的的字串(注意 001 後面的那一根短橫線,也是要匹配的物件),實心句號代表匹配除了換行符的任意字元,而星號則是匹配多個前面的字元,那麼 “*” 連起來的意思就是“匹配除換行符以外的多個字元”,re。I 的意思是進行大小寫敏感的匹配,當然在這段程式碼中並不重要。
同樣的功能,如果用 pandas 來實現,程式碼量會更少,可參考如下:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
#此處的檔案還是 supplier_data。csv
output_file
=
sys
。
argv
[
2
]
#此處的檔案是 pandas_value_matches_pattern_5output。csv
data_frame
=
pd
。
read_csv
(
input_file
)
data_frame_value_matches_pattern
=
data_frame
。
loc
[
data_frame
[
‘Invoice Number’
]
。
str
。
startswith
(
“001-”
),:]
data_frame_value_matches_pattern
。
to_csv
(
output_file
,
index
=
False
)
需要注意的是,我在用 CodeRunner 寫 pandas 時,總是不能自動補全,導致一些細節錯誤無法正常執行程式碼,不過,這也正好可以練習一下寫程式碼的基本功,畢竟一些常用的功能模組是需要記住的,不能都靠程式碼補全功能。
2。3 選取特定的列
2。3。1 列索引值
在 CSV 檔案中選取特定的列的一種辦法就是使用對應列的索引值,當然,這有一些限定條件,比如:
想保留的列的索引值非常容易識別到;
處理多個輸入檔案時,各個輸入檔案中列的位置一致。
對於 supplier_data。csv 檔案中,如果我們想只保留供應商名稱和成本這兩列,我們就可以使用索引值來選取這兩列,書中將以下程式碼儲存為 6csv_reader_column_by_index。py:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
#此處為 supplier_data。csv
output_file
=
sys
。
argv
[
2
]
#此處為 6output。csv
my_columns
=
[
0
,
3
]
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
for
row_list
in
filereader
:
row_list_output
=
[]
for
index_value
in
my_columns
:
row_list_output
。
append
(
row_list
[
index_value
])
filewriter
。
writerow
(
row_list_output
)
完成以上程式碼後,在資料資料夾中建立一個空白的 6output。csv 檔案,然後終端執行如下命令:
先導航到 。py 檔案所在路徑下
cd /Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料
執行此命令:
python3 6csv_reader_column_by_index。py supplier_data。csv 6output。csv
終端不會有任何輸出,但是此時開啟 6output。csv 檔案,就能看到供應商名稱和對應的價格了。
同樣的功能,我們來稍微做個小練習,找到一個名為 DEvideos。csv 的檔案,此檔案是從 Kaggle 網站下載的 YouTube 觀看資料之一,開啟檢查一下這個 csv 檔案,第一排有 video_id,trending_date,title,channel_title,category_id 等等資訊,現在的任務是,要抓取比較多的資料,具體要求如下:
抓取的資料應該包括除了 description 以外的所有資料;
對於 publish_time,僅保留日期,不用保留具體時間;
每抓取一條資訊,都在終端列印下來。
為了處理這個問題,我們依次來看要求,首先是抓取的資料要除開 description,這個比較好辦,一共有 16 列的資料,而且 description 剛好在最後,只要一個 range 函式就可以了;其次是 publish_time,僅保留日期,不需要時間,這個稍微麻煩一點點,需要用到正則匹配;最後是列印下來,這個沒什麼難度了,就是直接列印,只是在實際列印的過程中,我發現如果不列印,那麼程度處理得會很快,如果列印,CodeRunner 出現了轉綵球的狀況,為了演示,我們加入一個簡單的延時 time。sleep(0。001),在實際操作時可以根據自己的情況酌情考慮是否延時,請看下面的程式碼:
import
csv
import
re
import
time
pattern
=
re
。
compile
(
‘T[\d]+:[\d]+:[\S]+’
)
# 正則表示式,用來篩選 publish_time 中的日期
“”“實在不想在寫好程式碼後,跑去終端執行,就直接把路徑寫下來吧”“”
input_file
=
‘/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/Trending_YouTube_Video_Statistics/DEvideos。csv’
output_file
=
‘/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/DEvideo_write_column_by_index。csv’
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
for
row_list
in
filereader
:
row_list_output
=
[]
for
i
in
range
(
15
):
if
i
==
5
:
row_list
[
i
]
=
re
。
sub
(
pattern
,
‘’
,
row_list
[
i
])
#將 publish_time 中日期後面的部分用替換的方式刪除掉
row_list_output
。
append
(
row_list
[
i
])
filewriter
。
writerow
(
row_list_output
)
(
row_list_output
)
time
。
sleep
(
0。001
)
書中還給出了利用 pandas 挑選指定列的方法,比使用 csv 會更加簡單,如下所示:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
data_frame
=
pd
。
read_csv
(
input_file
)
data_frame_column_by_index
=
data_frame
。
iloc
[:,[
0
,
3
]]
data_frame_column_by_index
。
to_csv
(
output_file
,
index
=
False
)
很明顯,用 pandas 會比用 csv 更加簡單,程式碼量也更少。
2。3。2 列標題
除了用索引選取特定列以外,還可以在 csv 檔案中使用列標題來選取特定的列,可參考如下程式碼:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
my_columns
=
[
‘Invoice Number’
,
‘Purchase Date’
]
my_columns_index
=
[]
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header
=
next
(
filereader
,
None
)
for
index_value
in
range
(
len
(
header
)):
if
header
[
index_value
]
in
my_columns
:
my_columns_index
。
append
(
index_value
)
filewriter
。
writerow
(
my_columns
)
for
row_list
in
filereader
:
row_list_output
=
[]
for
index_value
in
my_columns_index
:
row_list_output
。
append
(
row_list
[
index_value
])
filewriter
。
writerow
(
row_list_output
)
原書的程式碼有一處錯誤,倒數第二排的 row_list_output。append(row_list[index_value]) 沒有縮排。另外,最後一排程式碼,filewriter。writerow(row_list_output),我不清楚是我機器的問題還是書中程式碼的問題,這一段程式碼也需要縮排到第二個 for 迴圈下,而不是第一個 for 迴圈下,如果不縮排,那麼在我的 Mac 上執行時,只讀取到了 supplier_data。csv 中最後一排的發票和價格。
2。4 選取連續的行
書中提到有時我們可能會遇到工作表的頭部和尾部都是不想處理的資訊,此時需要選擇那些我們需要處理的資料,可參考以下程式碼:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
# 此處是 supplier_data_unnecessary_header_footer。csv
output_file
=
sys
。
argv
[
2
]
# 此處是 11output。csv
row_counter
=
0
with
open
(
input_file
,
‘r’
,
newline
=
‘’
)
as
csv_in_file
:
with
open
(
output_file
,
‘w’
,
newline
=
‘’
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
for
row
in
filereader
:
if
row_counter
>=
3
and
row_counter
<=
15
:
filewriter
。
writerow
([
value
。
strip
()
for
value
in
row
])
row_counter
+=
1
以上程式碼可以在 supplier_data_unnecessary_header_footer。csv 這個檔案中跳過行開頭的資料,過濾行結尾的資料,只選擇我們需要的部分。
另外,在實際操作過程中,我將 supplier_data。csv 另存為一個新的 csv 檔案,新增三行 “I don‘t care this line” 到表頭,又新增三行“I don’t care this line either” 到表尾,並儲存成 supplier_data_unnecessary_header_footer。csv 時,執行上述程式碼遇到一個問題,錯誤提示是“UnicodeDecodeError: ‘utf-8’ codec can‘t decode byte 0xd5 in position 5: invalid continuation byte”,網上查了一下這應該是 utf-8 的解碼問題,如果您也遇到了類似問題,不妨試試我的方法:把所有單元格的內容整體複製下來,新建一個 csv 檔案貼上進去,我是這樣解決的。
剛剛是用 Python 自帶的 csv 庫完成了篩選特定行的操作,如果用 Pandas 的話,其實會更加簡單,如下所示:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
# 此處為 supplier_data_unnecessary_header_footer。csv
output_file
=
sys
。
argv
[
2
]
# 此處為 pandas_output_select_contiguous_rows。csv
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
16
,
17
,
18
])
data_frame
。
columns
=
data_frame
。
iloc
[
0
]
indexInfo
=
data_frame
。
index
。
drop
(
3
)
data_frame
=
data_frame
。
reindex
(
data_frame
。
index
。
drop
(
3
))
# 為了弄懂這一行程式碼的含義,我在 csv 檔案中加了一列 indextest 索引,從 1 到 12
data_frame
。
to_csv
(
output_file
,
index
=
False
)
當然,一開始我是沒有弄懂這段程式碼的,比如這一句
data_frame
=
data_frame
。
reindex
(
data_frame
。
index
。
drop
(
3
))
到底能達到什麼目的?在網上查了一下,drop() 函式的功能是把 data_frame 中對應的行或列的值拋掉,那麼
data_frame。index。drop(3)
是什麼鬼,開啟已經操作好的 pandas_output_select_contiguous_rows。csv 檔案,它是長這個樣子的:
| Supplier Name | Invoice Number | Part Number | Cost | Purchase Date | indextest | | ——————- | ———————— | ——————- | —————— | ——————- | ————- | | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 1 | | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 2 | | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 3 | | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 4 | | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 5 | | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 6 | | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 7 | | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 8 | | Supplier Z | 920-4803 | 3321 | $615。00 | 2002/3/14 | 9 | | Supplier Z | 920-4803 | 3321 | $615。00 | 2002/10/14 | 10 | | Supplier Z | 920-4803 | 3321 | $60,15。00 | 2/17/14 | 11 | | Supplier Z | 920-4803 | 3321 | $10,06015。00 | 2/24/14 | 12 |
換句話講,書中的 pandas 程式碼的確達到了我們想要的結果,即篩選特定行的資料,我們試試看逐個測試這段程式碼,並將結果列印下來,看看是怎樣。
首先把
input_file
和
output_file
都直接表示為路徑,即
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
這一步操作是為了可以直接在 CodeRunner 中執行出結果,省掉了終端命令的過程。
接下來,分別在每一行程式碼的下方加一句
print()
,檢視當前狀態下的各種資訊是怎樣的:
# 第一次 print
import
pandas
as
pd
import
sys
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
(
data_frame
)
此時的輸出是長這樣:
| | 0 | 1 | 。。。 | 4 | 5 | | —— | ——————————————- | ———————— | —— | ——————- | ————- | | 0 | I don’t care this line | NaN | 。。。 | NaN | NaN | | 1 | I don‘t care this line | NaN | 。。。 | NaN | NaN | | 2 | I don’t care this line | NaN | 。。。 | NaN | NaN | | 3 | Supplier Name | Invoice Number | 。。。 | Purchase Date | indextest | | 4 | Supplier X | 001-1001 | 。。。 | 1/20/14 | 1 | | 5 | Supplier X | 001-1001 | 。。。 | 1/20/14 | 2 | | 6 | Supplier X | 001-1001 | 。。。 | 1/20/14 | 3 | | 7 | Supplier X | 001-1001 | 。。。 | 1/20/14 | 4 | | 8 | Supplier Y | 50-9501 | 。。。 | 1/30/14 | 5 | | 9 | Supplier Y | 50-9501 | 。。。 | 1/30/14 | 6 | | 10 | Supplier Y | 50-9505 | 。。。 | 2002/3/14 | 7 | | 11 | Supplier Y | 50-9505 | 。。。 | 2002/3/14 | 8 | | 12 | Supplier Z | 920-4803 | 。。。 | 2002/3/14 | 9 | | 13 | Supplier Z | 920-4804 | 。。。 | 2002/10/14 | 10 | | 14 | Supplier Z | 920-4805 | 。。。 | 2/17/14 | 11 | | 15 | Supplier Z | 920-4806 | 。。。 | 2/24/14 | 12 | | 16 | I don‘t care this line either | NaN | 。。。 | NaN | NaN | | 17 | I don’t care this line either | NaN | 。。。 | NaN | NaN | | 18 | I don‘t care this line either | NaN | 。。。 | NaN | NaN |
利用
data_frame = pd。read_csv(input_file,header=None)
這一句程式碼,將 supplier_data_unnecessary_header_footer。csv 檔案中的所有資訊賦值給了 data_frame,不得不說真心方便。
# 第二次 print
import
pandas
as
pd
import
sys
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
16
,
17
,
18
])
(
data_frame
)
此時的輸出已經沒有了開頭的
I don’t care this line
和結尾的
I don‘t care this line either
,其輸出為:
| | 0 | 1 | 2 | 3 | 4 | 5 | | —— | ——————- | ———————— | ——————- | —————— | ——————- | ————- | | 3 | Supplier Name | Invoice Number | Part Number | Cost | Purchase Date | indextest | | 4 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 1 | | 5 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 2 | | 6 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 3 | | 7 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 4 | | 8 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 5 | | 9 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 6 | | 10 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 7 | | 11 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 8 | | 12 | Supplier Z | 920-4803 | 3321 | $615。00 | 2002/3/14 | 9 | | 13 | Supplier Z | 920-4804 | 3321 | $615。00 | 2002/10/14 | 10 | | 14 | Supplier Z | 920-4805 | 3321 | $60,15。00 | 2/17/14 | 11 | | 15 | Supplier Z | 920-4806 | 3321 | $10,06015。00 | 2/24/14 | 12 |
上面的程式碼用了一段 drop 函式
data_frame = data_frame。drop([0,1,2,16,17,18])
將第 0,1,2,16,17,18 行資料刪除掉。
然後我們再來看看
data_frame。iloc[0]
能幹什麼,書上是說可以使用 iloc 這個函式根據行索引選取一個單獨行作為列索引,那麼使用 iloc[0] 應該就是把第 0 行的各個單元格的值作為索引,從實際程式碼來看,也確實如此:
# 第三次 print
import
pandas
as
pd
import
sys
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
16
,
17
,
18
])
data_frame
。
columns
=
data_frame
。
iloc
[
0
]
(
data_frame
)
這一次
把上一次列印的第一排的 0 1 2 3 4 5 換成了列標題,如下所示:
| 3 | Supplier Name | Invoice Number | Part Number | Cost | Purchase Date | indextest | | —— | ——————- | ———————— | ——————- | —————— | ——————- | ————- | | 3 | Supplier Name | Invoice Number | Part Number | Cost | Purchase Date | indextest | | 4 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 1 | | 5 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 2 | | 6 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 3 | | 7 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 4 | | 8 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 5 | | 9 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 6 | | 10 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 7 | | 11 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 8 | | 12 | Supplier Z | 920-4803 | 3321 | $615。00 | 2002/3/14 | 9 | | 13 | Supplier Z | 920-4804 | 3321 | $615。00 | 2002/10/14 | 10 | | 14 | Supplier Z | 920-4805 | 3321 | $60,15。00 | 2/17/14 | 11 | | 15 | Supplier Z | 920-4806 | 3321 | $10,06015。00 | 2/24/14 | 12 |
我試過把程式碼中的
iloc[0]
換成
iloc[1]
,此時上面的列標題也會隨之更換,意味著我們透過
iloc[]
這個函式實現了重新根據行索引選取一個單獨行來作為列索引,可是明明在
data_frame = data_frame。drop([0,1,2,16,17,18])
中不是已經把第 0 行丟掉了麼?是的,丟掉了,
iloc[0]
在這段程式碼裡面指的也不是最初的第 0 行,請看錶格最左的數字,
iloc[0]
指代的是數字 3 那一行。
再做一個小測試,在上一次列印時我們可以看到索引為 3 的那一行重複了,那麼如果在
data_frame。columns = data_frame。iloc[0]
的下面加一行,把表格重複的內容去掉,應該如何操作?我試過
data_frame = data_frame。drop(3)
是可行的,下面是具體程式碼和列印下來的表格:
# 第四次 print
import
pandas
as
pd
import
sys
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
16
,
17
,
18
])
data_frame
。
columns
=
data_frame
。
iloc
[
0
]
data_frame
=
data_frame
。
drop
(
3
)
(
data_frame
)
| 3 | Supplier Name | Invoice Number | Part Number | Cost | Purchase Date | indextest | | —— | ——————- | ———————— | ——————- | —————— | ——————- | ————- | | 4 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 1 | | 5 | Supplier X | 001-1001 | 2341 | $500。00 | 1/20/14 | 2 | | 6 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 3 | | 7 | Supplier X | 001-1001 | 5467 | $750。00 | 1/20/14 | 4 | | 8 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 5 | | 9 | Supplier Y | 50-9501 | 7009 | $250。00 | 1/30/14 | 6 | | 10 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 7 | | 11 | Supplier Y | 50-9505 | 6650 | $125。00 | 2002/3/14 | 8 | | 12 | Supplier Z | 920-4803 | 3321 | $615。00 | 2002/3/14 | 9 | | 13 | Supplier Z | 920-4804 | 3321 | $615。00 | 2002/10/14 | 10 | | 14 | Supplier Z | 920-4805 | 3321 | $60,15。00 | 2/17/14 | 11 | | 15 | Supplier Z | 920-4806 | 3321 | $10,06015。00 | 2/24/14 | 12 |
這證明
data_frame。drop(3)
這段命令的確把多餘的 Supplier Name 那一行去掉了,注意去掉的是第二個 Supplier Name 那一行,並不是第一個。
好了,測試完成,讓我們回到書中程式碼本身,並再來一個列印,看看究竟有什麼變化:
# 第五次 print
import
pandas
as
pd
import
sys
input_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/supplier_data_unnecessary_header_footer。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
output_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv‘
# 請注意此處需要替換為您自己電腦上對應檔案的路徑
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
16
,
17
,
18
])
data_frame
。
columns
=
data_frame
。
iloc
[
0
]
data_frame
=
data_frame
。
reindex
(
data_frame
。
index
。
drop
(
3
))
# 為了弄懂這一行程式碼的含義,我在 csv 檔案中加了一列 indextest 索引,從 1 到 12
(
data_frame
)
第五次
中,主要就是解釋
data_frame = data_frame。reindex(data_frame。index。drop(3))
這一段的含義,按照剛剛小測試的結果,
data_frame。index。drop(3)
丟掉了第二個 Supplier Name 那一行,而
data_frame。reindex()
的含義就是重新建立索引,合起來就是,先刪掉多餘的 Supplier Name,再重建索引,其列印結果與上面的那個表格一致,最後再寫入到新的 csv 檔案即可。
2。5 新增標題行
這一節的意思是,有時我們會收到沒有標題行的 csv 資料,需要自己新增標題行,其實有時候也會遇到需要修改標題行的情況,可參考以下程式碼新增標題行:
import
sys
import
csv
“”“
開始之前,請開啟 supplier_data。csv 檔案,把標題行刪了,然後將此檔案另存為supplier_data_no_header_row。csv
”“”
input_file
=
sys
。
argv
[
1
]
# 此處為 supplier_data_no_header_row。csv
output_file
=
sys
。
argv
[
2
]
# 此處為 12output。csv
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
with
open
(
output_file
,
’w‘
,
newline
=
’‘
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
header_list
=
[
’Supplier Name‘
,
’Invoice Number‘
,
’Part Number‘
,
’Cost‘
,
’Purchase Date‘
]
filewriter
。
writerow
(
header_list
)
for
row
in
filereader
:
filewriter
。
writerow
(
row
)
同樣的功能,如果用 pandas 會更簡單一些,畢竟書中的原話是“pandas 中的 read_csv 可以直接指定輸入檔案不包含標題行,並可以提供一個列標題列表”,可參考以下程式碼:
import
pandas
as
pd
import
sys
input_file
=
sys
。
argv
[
1
]
# 此處為 supplier_data_no_header_row。csv
output_file
=
sys
。
argv
[
2
]
# 此處為 pandas_add_header_row_output。csv
header_list
=
[
’Supplier Name‘
,
’Invoice Number‘
,
’Part Number‘
,
’Cost‘
,
’Purchase Date‘
]
data_frame
=
pd
。
read_csv
(
input_file
,
header
=
None
,
names
=
header_list
)
data_frame
。
to_csv
(
output_file
,
index
=
False
)
2。6 讀取多個 CSV 檔案
書中需要讓讀者自行建立三個 csv 檔案,分別是 sales_january_2014。csv,sales_february_2014。csv,sales_march_2014。csv,所建立的 csv 檔案內容是 Customer ID,Customer Name,Invoice Number,Sale Amount,Purchase Date 這種資訊,那麼我們不妨結合一下之前所學過的內容,試試看統計一下我自己從國家統計局獲取的房地產開發投資情況,這裡面我已經準備好了 excel 檔案和 csv 檔案,稍後我們會先從 csv 檔案入手。
檔案計數與檔案中的行列計數
開始之前先看看兩個要用到的東西,一個是 glob 庫,一個是 os。path。join() 函式。
首先看一個 Python 自帶的庫 glob,參考了一下網上的資料,這個庫的功能就是獲取當前資料夾下的子檔案和子資料夾,用 * 作萬用字元匹配,比如下面的程式碼:
import
glob
testpath
=
’/*‘
#獲取根目錄下的所有資料夾
for
name
in
glob
。
glob
(
testpath
):
(
name
)
這段程式碼會獲取我電腦上根目錄資料夾的所有子資料夾,並將其列印下來。再比如下面的程式碼會獲取我電腦上所有安裝的軟體名稱,不過是以路徑形式:
import
glob
testpath
=
’/Applications/*‘
#獲取應用程式目錄下的所有軟體
for
name
in
glob
。
glob
(
testpath
):
(
name
)
接著看看 os。path。join() 函式的功能,實際上它和字串的拼接有點像,但它主要是針對路徑的,如果路徑中沒有 \,它可以自動補全,舉個例子:
import
os
path1
=
’home‘
path2
=
’admin‘
path3
=
’document‘
path_final
=
os
。
path
。
join
(
path1
,
path2
,
path3
)
(
path_final
)
# 此時輸出的 path_final 就是 home/admin/document
明白了這些之後,如果我們想要看懂書上的 8csv_reader_counts_for_multiple_files。py 原始碼,還有一個需要了解,試想一下,如果我們要找某個資料夾中所有以 pandas 開頭的檔案,應該怎樣處理?我們試試看找一下之前我們儲存過的以 pandas 開頭的檔案:
import
os
import
glob
test_file
=
’/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料‘
# 注意此處要換成您自己電腦上對應的路徑
for
pandasfile
in
glob
。
glob
(
os
。
path
。
join
(
test_file
,
’pandas_*‘
)):
(
pandasfile
)
這裡用到了
glob。glob(os。path。join(test_file, ’pandas_*‘))
,實際上這就是我們要找的當前資料夾下所有以 pandas_ 開頭的檔案,用 * 表示通配查詢,列印下來的結果如下:
“”“
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_select_contiguous_rows。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_select_contiguous_rows。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_value_in_set。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_value_meets_condition。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_add_header_row_output。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_value_meets_condition。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_value_matches_pattern。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_column_by_index。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_parsing_and_write。gif
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_add_header_row。py
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_value_matches_pattern_5output。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output_column_by_index。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_output。csv
/Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/第2章所需資料/pandas_ parsing_and_write。py
”“”
好了,我們可以開始學習書中第 73 頁的程式碼了,原始碼如下:
import
csv
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
file_counter
=
0
for
input_file
in
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’sales_*‘
)):
row_counter
=
1
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
header
=
next
(
filereader
,
None
)
for
row
in
filereader
:
row_counter
+=
1
(
’
{0!s}
:
\t
{1:d}
rows
\t
{2:d}
columns‘
。
format
(
os
。
path
。
basename
(
input_file
),
row_counter
,
len
(
header
)))
file_counter
+=
1
(
’Number of files:
{0:d}
‘
。
format
(
file_counter
))
原書中說執行上面的程式碼需要在終端執行下面這段程式碼:
python 8csv_reader_counts_for_multiple_files。py “C:\Users\Clinton\Desktop”
由於我並沒有去建立 sales 檔案,且自己找到了一些資料,所以我將上面的程式碼稍微進行了一點修改,請看如下:
import
csv
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
file_counter
=
0
for
input_file
in
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’*csv‘
)):
# 此處把 sales_* 修改為 *csv
row_counter
=
1
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
header
=
next
(
filereader
,
None
)
for
row
in
filereader
:
row_counter
+=
1
(
’
{0!s}
:
\t
{1:d}
rows
\t
{2:d}
columns‘
。
format
(
os
。
path
。
basename
(
input_file
),
row_counter
,
len
(
header
)))
file_counter
+=
1
(
’Number of files:
{0:d}
‘
。
format
(
file_counter
))
然後開啟終端,導航至 8csv_reader_counts_for_multiple_files。py 所在的資料夾,並輸入如下命令:
python3 8csv_reader_counts_for_multiple_files。py /Users/jason/Documents/GitHub/NoteforPythonDataAnalyze/房地產開發投資情況/csvFile
此時終端就會輸出每個 csv 檔案有多少行多少列,最後會輸出一共有多少檔案。
2。7 從多個檔案中連線資料
實際處理資料時,可能會有多個檔案,這些檔案的內容格式一致,需要放在一起進行資料統計工作,比如全國各地提交給統計局的房地產資料,它們可能是“北京。csv”,“上海。csv”,“廣東。csv”這樣的命名方式。
在原書中是將三個 sales 檔案的資料合併到一起,我會在這裡貼上原始碼,同時附上我們自己修改後的程式碼。
基礎 Python
先附上原始碼:
import
csv
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
first_file
=
True
for
input_file
in
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’sales_*‘
)):
(
os
。
path
。
basename
(
input_file
))
# 列印當前處理檔案的名稱
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
with
open
(
output_file
,
’a‘
,
newline
=
’‘
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
if
first_file
:
for
row
in
filereader
:
filewriter
。
writerow
(
row
)
first_file
=
False
else
:
header
=
next
(
filereader
,
None
)
for
row
in
filereader
:
filewriter
。
writerow
(
row
)
根據上面的程式碼,我們試試把房地產開發投資情況中的各個 csv 檔案進行一下整合,要求如下:
所有的 csv 檔案中的資料需要整合到一張 csv 表裡面;
整合後的資料需要能夠看得出來是哪個省份在哪個時間的房地產資料;
對於單獨的每個 csv 檔案開頭幾行的資料庫地區時間這些,要剔除掉,最後一排的資料來源,每個表裡面都有,為了統計的需要,也不用每個都保留下來。
那麼我們先開啟其中任意一個 csv 檔案來看看,它都有些什麼資料,開啟的方式並不是直接雙擊這個 csv 檔案,因為我們要考慮到在實際工作中,我們可能要開啟十幾個以 GB 為單位的 csv 檔案,同時也為了練習一下剛才的程式碼,我們試試用 Python 來開啟並輸出其中一個檔案,此處就挑選內蒙古。csv 這個檔案,那麼程式碼可以這樣寫:
import
csv
import
sys
input_file
=
sys
。
argv
[
1
]
with
open
(
input_file
)
as
csv_in_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
for
row
in
filereader
:
for
cell
in
row
:
if
cell
:
(
cell
,
end
=
’
\t
‘
)
(
’‘
)
列印下來的資料大概長這個樣子:
| 資料庫:分省月度資料 | | | | | | | —————————————— | —————— | —————— | —————— | —— | ————- | | 地區:內蒙古自治區 | | | | | | | 時間:最近36個月 | | | | | | | 指標 | 2019年12月 | 2019年11月 | 2019年10月 | 。。。 | 2017年1月 | | 房地產投資累計值(億元) | 1041。95 | 1026。08 | 957。6 | 。。。 | | | 房地產投資累計增長(%) | 18 | 17。2 | 15。8 | 。。。 | | | 房地產住宅投資累計值(億元) | 782。13 | 769。18 | 702。77 | 。。。 | | | 房地產住宅投資累計增長(% | 21。8 | 20。7 | 16。9 | 。。。 | | | 資料來源:國家統計局 | | | | | |
在原始資料中,內蒙古剛好沒有 2917 年 1 月的相關房地產資料,這裡也給我們提了個醒,對於資料的整理工作,務必要注意,並不是每個單元格都是有資料的,也並不是每個單元格的資料都是正確的。
好了,開始正式的程式碼:
import
csv
import
glob
import
os
import
sys
import
re
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
first_file
=
True
for
input_file
in
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’*csv‘
)):
message
=
“Dealing with file ”
+
str
(
os
。
path
。
basename
(
input_file
))
# print(message)
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
with
open
(
output_file
,
’a‘
,
newline
=
’‘
)
as
csv_out_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
filewriter
=
csv
。
writer
(
csv_out_file
)
row_counter
=
1
if
first_file
:
for
row
in
filereader
:
if
row_counter
>
3
and
row_counter
<
9
:
# 對於第一個處理的檔案,前 3 行和第 9 行資料是不需要的,只保留從第 4 行開始的資料
if
row_counter
==
4
:
row
。
insert
(
0
,
“地區”
)
# 在第 4 行的開頭插入一個地區列,用來區分這些資料是哪個省市的
else
:
pattern
=
re
。
compile
(
“\。csv”
)
# 用正則表示式把檔名的 。csv 去掉,把檔名加入到地區那一列
location_name
=
re
。
sub
(
pattern
,
’‘
,
os
。
path
。
basename
(
input_file
))
row
。
insert
(
0
,
location_name
)
filewriter
。
writerow
(
row
)
row_counter
+=
1
first_file
=
False
else
:
for
row
in
filereader
:
if
row_counter
>
4
and
row_counter
<
9
:
pattern
=
re
。
compile
(
“\。csv”
)
location_name
=
re
。
sub
(
pattern
,
’‘
,
os
。
path
。
basename
(
input_file
))
row
。
insert
(
0
,
location_name
)
filewriter
。
writerow
(
row
)
row_counter
+=
1
pandas 連線多個檔案
首先我還是貼出書中的原始碼:
import
pandas
as
pd
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
all_files
=
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’sales_*‘
))
all_data_frames
=
[]
for
file
in
all_files
:
data_frame
=
pd
。
read_csv
(
file
,
index_col
=
None
)
all_data_frames
。
append
(
data_frame
)
data_frame_concat
=
pd
。
concat
(
all_data_frames
,
axis
=
0
,
ignore_index
=
True
)
data_frame_concat
。
to_csv
(
output_file
,
index
=
False
)
書中第 78 頁提到,這段程式碼是垂直堆疊資料框,如果需要平行連線資料,那麼就在 concat 函式中設定 axis=1。我想到了一個平行連線資料的實際需求,試想一下,如果要追蹤一群人每年的某些資料,比如 NBA 球星在每年的三分球,場均得分,助攻等等資料,而他們每年的資料是按年列的 csv 表格,就可能需要按平行連線資料,各位看官可以自己試試看。
貼出了原始碼,我仍然不會按照這段程式碼去執行,而是用 pandas 來整合房地產的資料,下面請看我自己修改過後的程式碼:
import
pandas
as
pd
import
glob
import
os
import
sys
import
re
finishwriting
=
“Finish writing data to file”
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
all_house_price_files
=
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’*csv‘
))
all_data_frames
=
[]
first_file
=
True
for
house_price_file
in
all_house_price_files
:
if
first_file
:
“”“以下程式碼是用來獲取當前處理 csv 檔案的檔名”“”
district_names
=
[]
file_name
=
os
。
path
。
basename
(
house_price_file
)
pattern
=
re
。
compile
(
’\。csv‘
)
district_name
=
re
。
sub
(
pattern
,
’‘
,
file_name
)
“”“還記得之前的 drop 函式和 iloc 函式麼,又用到他們來挑選指定行了”“”
data_frame
=
pd
。
read_csv
(
house_price_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
8
])
data_frame
。
columns
=
data_frame
。
iloc
[
0
]
data_frame
=
data_frame
。
reindex
(
data_frame
。
index
。
drop
(
3
))
“”“為了知道彙總後的資料都是哪個省市的房地產資料,需要提前插入一列地區”“”
for
row
in
range
(
data_frame
。
shape
[
0
]):
district_names
。
append
(
district_name
)
data_frame
。
insert
(
0
,
’地區‘
,
district_names
)
data_frame
。
to_csv
(
output_file
,
mode
=
’a‘
,
index
=
None
,
encoding
=
’utf-8-sig‘
)
# 需要注意,可能由於一些相容性問題,我的電腦上編碼居然是 utf-8-sig,不然可能出現寫入檔案亂碼
(
data_frame
)
first_file
=
False
else
:
district_names
=
[]
file_name
=
os
。
path
。
basename
(
house_price_file
)
pattern
=
re
。
compile
(
’\。csv‘
)
district_name
=
re
。
sub
(
pattern
,
’‘
,
file_name
)
data_frame
=
pd
。
read_csv
(
house_price_file
,
header
=
None
)
data_frame
=
data_frame
。
drop
([
0
,
1
,
2
,
3
,
8
])
for
row
in
range
(
data_frame
。
shape
[
0
]):
district_names
。
append
(
district_name
)
data_frame
。
insert
(
0
,
’地區‘
,
district_names
)
data_frame
。
to_csv
(
output_file
,
mode
=
’a‘
,
index
=
None
,
header
=
None
,
encoding
=
’utf-8-sig‘
)
# mode=’a‘ 意思就是向檔案中以附加的方式寫入資料,而不是覆蓋寫入
(
finishwriting
)
原始程式碼放在這裡,您也可以查詢對應寫入好的 pandas_concat_rows_from_multiple_files_in_房地產開發投資情況。csv 檔案,簡單說一下我在寫這段程式碼時遇到的問題,僅供各位參考:
每個省份或直轄市的資料是單獨存放在不同的 csv 檔案中的,如果我把它們整合到一起,那麼會不知道哪個資料屬於哪個省份,此時需要在對應資料的前面加一列代表省份或直轄市;
把所有資料合併到一起時,可能會出現有多個標題列,這並不是我們想看到的,可以設定一個 first_file = True 的旗標,當輸入為第一個檔案時,我們儲存這個標題列,當輸入不是第一個檔案時,我們用 header=None 來忽略掉標題列,只保留相應資料;
選擇合併的方法並不唯一,可以把所有資料都先整合到一個 all_data_frames 中再統一寫入檔案,也可以分別寫入資料,注意分別寫入時,data_frame。to_csv 的限定條件有一個 mode=’a‘,代表附加寫入,而不是覆蓋寫入;
亂碼問題,解決這個問題的最好辦法其實是在 csv 檔案中不要包含中文,因為 python 本身對中文的支援並不是很好,當然我們都知道,這在實際應用中是不可能的,你無法要求錄入資料的人一個漢字都不寫,所以要在處理資料時注意解碼問題。
2。8 計算每個檔案中值的總和與均值
有時候我們會收到很多檔案格式一致的資料,要求計算出裡面某個列的總和,對於單個檔案來講,我們可以直接使用一些內建的 Excel 函式,但是如果有多個檔案則會更復雜,比如某公司某年所有銷售點的營業額總和,所有銷售點營業額的均值,進一步可以計算每個銷售點的盈利能力等等,下面看看用 python 具體如何處理。
基礎 Python
import
csv
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
output_header_list
=
[
’file_name‘
,
’total_sales‘
,
’average_sales‘
]
# 建立一個輸出檔案的列標題列表
csv_out_file
=
open
(
output_file
,
’a‘
,
newline
=
’‘
)
filewriter
=
csv
。
writer
(
csv_out_file
)
filewriter
。
writerow
(
output_header_list
)
# 將標題行寫入輸出檔案
for
input_file
in
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’sales_*‘
)):
with
open
(
input_file
,
’r‘
,
newline
=
’‘
)
as
csv_in_file
:
filereader
=
csv
。
reader
(
csv_in_file
)
output_list
=
[]
# 用來儲存要寫入輸出檔案中的每行輸出
output_list
。
append
(
os
。
path
。
basename
(
input_file
))
# 您可以試試看,這個地方保留的 input_file 是有後綴 。csv 的,那麼寫入到輸出檔案時怎麼去除後續呢
header
=
next
(
filereader
)
# next() 函式去除每個輸入檔案的標題行
total_sales
=
0。0
number_of_sales
=
0。0
for
row
in
filereader
:
sale_amount
=
row
[
3
]
total_sales
+=
float
(
str
(
sale_amount
)
。
strip
(
’$‘
)
。
replace
(
’,‘
,
’‘
))
number_of_sales
+=
1
average_sales
=
’
{0:。2f}
‘
。
format
(
total_sales
/
number_of_sales
)
output_list
。
append
(
total_sales
)
output_list
。
append
(
average_sales
)
filewriter
。
writerow
(
output_list
)
csv_out_file
。
close
()
上面這段程式碼儲存為 10csv_reader_sum_average_from_multiple_files。py,已經放在我的 GitHub 倉庫中了,對應的 csv 檔案 10output。csv 也在倉庫中。
pandas 實現
相較於 Python 的基礎 csv 庫,Pandas 提供了 sum() 和 mean() 這兩個摘要統計函式。可參考如下程式碼:
import
pandas
as
pd
import
glob
import
os
import
sys
input_path
=
sys
。
argv
[
1
]
output_file
=
sys
。
argv
[
2
]
all_files
=
glob
。
glob
(
os
。
path
。
join
(
input_path
,
’sales_*‘
))
all_data_frames
=
[]
for
input_file
in
all_files
:
data_frame
=
pd
。
read_csv
(
input_file
,
index_col
=
None
)
total_cost
=
pd
。
DataFrame
([
float
(
str
(
value
)
。
strip
(
’$‘
)
。
replace
(
’,‘
,
’‘
))
for
value
in
data_frame
。
loc
[:,
’Sale Amount‘
]])
。
sum
()
average_cost
=
pd
。
DataFrame
([
float
(
str
(
value
)
。
strip
(
’$‘
)
。
replace
(
’,‘
,
’‘
))
for
value
in
data_frame
。
loc
[:,
’Sale Amount‘
]])
。
mean
()
data
=
{
’file_name‘
:
os
。
path
。
basename
(
input_file
),
’total_sales‘
:
total_cost
,
’average_sales‘
:
average_cost
}
# 原書中這裡寫的是 ’total_sales‘:total_sales 和 ’average_sales‘:average_sales,估計是作者的編譯器自動填充錯了
all_data_frames
。
append
(
pd
。
DataFrame
(
data
,
columns
=
[
’file_name‘
,
’total_sales‘
,
’average_sales‘
]))
data_frames_concat
=
pd
。
concat
(
all_data_frames
,
axis
=
0
,
ignore_index
=
True
)
data_frames_concat
。
to_csv
(
output_file
,
index
=
False
)
2。9 本章練習
第 2 章到這裡也就結束了,我們主要學習了對 csv 檔案的各種操作,比如選取特定行列,選取連續行列,讀取多個 csv 檔案,計算總和與均值等等,書中也給我們留下了一些練習。此處不對這些練習進行解析,只把它們寫下來。
對根據具體條件、集合和正則表示式來篩選行資料的一個指令碼進行修改,將與示例程式碼中不同的一組資料打印出來並寫入輸出檔案。
對根據索引值或列標題來篩選列資料的一個指令碼進行修改,將與示例程式碼中不同的一組資料打印出來並寫入輸出檔案。
在一個資料夾中建立一組新的 CSV 輸入檔案,建立另外一個輸出資料夾,使用處理多個檔案的一個指令碼來處理這些新的輸入檔案,並將結果寫入輸出資料夾。