pandas提供了很多的方式去創建dataframe,例如接收字典,讀取csv或excel格式文件。
在讀取本地文件的時候,可以直接調用讀取csv的方法,但是多數情況下,csv文件是作爲一種模型的訓練數據存儲在hdfs上,pandas是沒有辦法直接讀取hdfs上的數據的,需要通過hdfs客戶端從hdfs上讀取文件內容,再想辦法轉爲pandas的dataframe。
- 之前用的兩種方式:
- 一種是讀取hdfs的csv,然後寫入本地文件,再利用pandas讀取這個本地文件返回一個dataframe。
import hdfs import pandas as pd hdfs_user = "root" hdfs_addr = "http://centos121:9870" cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user) hdfs_path = "/test/a.csv" with cli.read(hdfs_path, encoding='utf-8')as reader: res_list = list() for row in reader: res_list.append(row) with open("D:\\b.csv", 'w', encoding='utf-8')as f: f.writelines(res_list) df = pd.read_csv("D:\\b.csv") print(df)
-
第二種是讀取hdfs的csv,然後存到一個列表中,然後逐行處理字符串,最終得到一個二維數組,然後將二維數組傳給pandas得到一個dataframe。
import hdfs import pandas as pd import re hdfs_user = "root" hdfs_addr = "http://centos121:9870" cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user) hdfs_path = "/test/a.csv" def split_by_dot_escape_quote(string): """ 按逗號分隔字符串,若其中有引號,將引號內容視爲整體 """ # 匹配引號中的內容,非貪婪,採用正向肯定環視, # 當左引號(無論單雙引)被匹配到,放入組quote, # 中間的內容任意,但是要用+?,非貪婪,且至少有一次匹配到字符, # 若*?,則匹配0次也可,並不會匹配任意字符(環視只匹配位置不匹配字符), # 由於在任意字符後面又限定了前面匹配到的quote,故只會匹配到", # +?則會限定前面必有字符被匹配,故"",或引號中任意值都可匹配到 pattern = '(?=(?P<quote>[\'\"])).+?(?P=quote)' rs = re.finditer(pattern, string) for data in rs: # 匹配到的字符串 old_str = data.group() # 將匹配到的字符串中的逗號替換爲特定字符, # 以便還原到原字符串進行替換 new_str = old_str.replace(',', '${dot}') # 由於匹配到的引號僅爲字符串申明,並不具有實際意義, # 需要把匹配時遇到的引號都去掉,只替換掉當前匹配組的引號 new_str = re.sub(data.group('quote'), '', new_str) string = string.replace(old_str, new_str) sps = string.split(',') return map(lambda x: x.replace('${dot}', ','), sps) def read_hdfs(hdfs_path, random_num_list=None): head_str = '' # 表頭 res_list = [] # 表格數據 with cli.read(hdfs_path, encoding='utf-8') as reader: index = 0 for i, row_data in enumerate(reader): if random_num_list is not None and len(random_num_list) <= 0: break if i == 0: # 取第一行表頭 head_str = re.sub('\r|\n|\t', '', row_data) else: # 去掉表頭後的數據 index += 1 # 非空值數據當前行號 if random_num_list is not None and len(random_num_list) > 0 and index == random_num_list[0]: res_list.append(re.sub('\r|\n|\t', '', row_data)) random_num_list.remove(random_num_list[0]) elif random_num_list is None: res_list.append(re.sub('\r|\n|\t', '', row_data)) src_list = list(map(split_by_dot_escape_quote, res_list)) return pd.DataFrame(src_list, columns=head_str.split(',')) print(read_hdfs(hdfs_path))
第二種方式,大多數操作都是自己在處理每種情況,一是出錯率較高,二是比較繁瑣,由於代碼水平有限,執行效率也不如pandas直接讀取高。
- 一種是讀取hdfs的csv,然後寫入本地文件,再利用pandas讀取這個本地文件返回一個dataframe。
-
後來想到一種改良第一種方式的辦法,不寫入文件,只創建一個文件對象用來存儲讀到的內容,然後將這個文件對象傳給pandas,事實證明這是可行的。因爲本質上pandas讀取csv得到的也是一個在內存中的文件對象,這樣的話我就不需要再用正則去判斷諸多可能導致創建df失敗的情況了。
from io import StringIO import hdfs import pandas as pd hdfs_user = "root" hdfs_addr = "http://centos121:9870" hdfs_path = "/test/a.csv" cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user) with cli.read(hdfs_path, encoding='utf-8')as reader: res_list = '' for row in reader: res_list += row pd.read_csv(StringIO(res_list))
StringIO就是用來在內存中讀寫字符串,它會返回一個文件對象,正好可以傳入pandas的read_csv,這樣就實現了不需要和磁盤io的過程,減少了開銷。