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的过程,减少了开销。