Python处理复杂的CSV文件

简介: Python处理复杂的CSV文件

Python处理复杂的CSV文件

目录

大家好 我是毕加索锁(锁!)今天教大家 利用Python处理复杂的CSV文件

项目简介

项目笔记与心得

总结

大家好 我是毕加索锁(锁!)今天教大家 利用Python处理复杂的CSV文件
项目简介
鉴于项目保密的需要,不便透露太多项目的信息,因此,简单介绍一下项目存在的难点:

海量数据:项目是对CSV文件中的数据进行处理,而特点是数据量大...真的大!!!拿到的第一个CSV示例文件是110多万行(小CASE),而第二个文件就到了4500万行,等到第三个文件......好吧,一直没见到第三个完整示例文件,因为太大了,据说是第二个示例文件的40多倍,大概二十亿行......

业务逻辑复杂:项目是需要对CSV文件的每一行数据的各种组合可能性进行判断,而判断的业务逻辑较为复杂,如何在解决复杂逻辑的同时保证较高的处理效率是难点之一。

项目笔记与心得
一、分批处理与多进程、多线程加速

因为数据量太大,肯定是要分批对数据进行处理,否则,效率低不谈,大概率也没有足够的内存能够支撑,需要用到chunksize,此外,为了节约内存,以及提高处理效率,可以将文本类的数据存储为“category”格式:

项目整体是计算密集型的任务,因此,需要用到多进程,充分利用CPU的多核性能;

多线程进行读取与写入,其中,写入使用to_csv的增量写入方法,mode参数设置为'a';

多进程与多线程开启一般为死循环,需要在合适的位置,放入结束循环的信号,以便处理完毕后退出多进程或多线程

"""鉴于项目保密需要,以下代码仅为示例"""
import time
import pathlib as pl
import pandas as pd
from threading import Thread
from multiprocessing import Queue, Process, cpu_count

导入多线程Thread,多进程的队列Queue,多进程Process,CPU核数cpu_count

存放分段读取的数据队列,注:maxsize控制队列的最大数量,避免一次性读取到内存中的数据量太大

data_queue = Queue(maxsize=cpu_count() * 2)

存放等待写入磁盘的数据队列

write_queue = Queue()

def read_data(path: pl.Path, data_queue: Queue, size: int = 10000):

"""
读取数据放入队列的方法
:return:
"""
data_obj = pd.read_csv(path, sep=',', header=0, chunksize=size, dtype='category')
for idx, df in enumerate(data_obj):
    while data_queue.full():  # 如果队列满了,那就等待
        time.sleep(1)
    data_queue.put((idx + 1, df))
data_queue.put((None, None))  # 放入结束信号


def write_data(out_path: pl.Path, write_queue: Queue):

"""
将数据增量写入CSV的方法
:return:
"""
while True:
    while write_queue.empty():
        time.sleep(1)
    idx, df = write_queue.get()
    if df is None:
        return  # 结束退出
    df.to_csv(out_path, mode='a', header=None, index=False, encoding='ansi')  # 输出CSV
        

def parse_data(data_queue: Queue, write_queue: Queue):

"""
从队列中取出数据,并加工的方法
:return:
"""
while True:
    while write_queue.empty():
        time.sleep(1)
    idx, df = data_queue.get()
    if df is None:  # 如果是空的结束信号,则结束退出进程,
    # 特别注意结束前把结束信号放回队列,以便其他进程也能接收到结束信号!!!
        data_queue.put((idx, df))
        return
    """处理数据的业务逻辑略过"""
    write_queue.put((idx, df))  # 将处理后的数据放入写队列


创建一个读取数据的线程

read_pool = Thread(target=read_data, args=(read_data_queue, *args))
read_pool.start() # 开启读取线程

创建一个增量写入CSV数据的线程

write_pool = Thread(target=write_data, args=(write_data_queue, *args))
write_pool.start() # 开启写进程

pools = [] # 存放解析进程的队列
for i in range(cpu_count()): # 循环开启多进程,不确定开多少个进程合适的情况下,那么按CPU的核数开比较合理

pool = Process(target=parse_data, args=(read_data_queue, write_data_queue, *args))
pool.start()  # 启动进程
pools.append(pool)  # 加入队列

for pool in pools:

pool.join()  # 等待所有解析进程完成

所有解析进程完成后,在写队列放入结束写线程的信号

write_data_queue.put((None, None))
write_pool.join() # 等待写线程结束
print('任务完成')
二、优化算法,提高效率

将类对象存入dataframe列

在尝试了n种方案之后,最终使用了将类对象存到dataframe的列中,使用map方法,运行类方法,最后,将运行结果展开到多列中的方式。该方案本项目中取得了最佳的处理效率。

"""鉴于保密需要,以下代码仅为示例"""
class Obj:

def __init__(self, ser: pd.Series):
    """
    初始化类对象
    :param ser: 传入series
    """
    self.ser = ser  # 行数据
    self.attrs1 = []  # 属性1
    self.attrs2 = []  # 属性2
    self.attrs3 = []  # 属性3

def __repr__(self):
    """
    自定义输出
    """
    attrs1 = '_'.join([str(a) for a in self.attrs1])
    attrs2 = '_'.join([str(a) for a in self.attrs2])
    attrs3 = '_'.join([str(a) for a in self.attrs3])
    return '_'.join([attrs1, attrs2, attrs3])
    
def run(self):
    """运行业务逻辑"""
    

创建obj列,存入类对象

data['obj'] = data.apply(lambda x: Obj(x), axis=1)

运行obj列中的类方法获得判断结果

data['obj'] = data['obj'].map(lambda x: x.run())

链式调用,1将类对象文本化->2拆分到多列->3删除空列->4转换为category格式

data[['col1', 'col2', 'col3', ...省略]] = data['obj'].map(str).str.split('_', expand=True).dropna(axis=1).astype('category')

删除obj列

data.drop(columns='obj', inplace=True)
减少计算次数以提高运行效率

在整个优化过程中,对运行效率产生最大优化效果的有两项:

一是改变遍历算法,采用直接对整行数据进行综合判断的方法,使原需要遍历22个组合的计算与判断大大减少

二是提前计算特征组合,制作成字典,后续直接查询结果,而不再进行重复计算

使用numpy加速计算

numpy还是数据处理上的神器,使用numpy的方法,比自己实现的方法效率要高非常多,本项目中就用到了:bincount、argsort,argmax、flipud、in1d、all等,即提高了运行效率,又解决了逻辑判断的问题:

"""numpy方法使用示例"""
import numpy as np

计算数字的个数组合bincount

np.bincount([9, 2, 13, 12, 9, 10, 11])

输出结果:array([0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1], dtype=int64)

取得个数最多的数字argmax

np.argmax(np.bincount([9, 2, 13, 12, 9, 10, 11]))

输出结果: 9

将数字按照个数优先,其次大小进行排序argsort

np.argsort(np.bincount([9, 2, 13, 12, 9, 10, 11]))

输出结果:array([ 0, 1, 3, 4, 5, 6, 7, 8, 2, 10, 11, 12, 13, 9], dtype=int64)

翻转列表flipud

np.flipud(np.argsort(np.bincount([9, 2, 13, 12, 9, 10, 11])))

输出结果: array([ 9, 13, 12, 11, 10, 2, 8, 7, 6, 5, 4, 3, 1, 0], dtype=int64)

查找相同值in1d

np.in1d([2, 3, 4], [2, 9, 3])

输出结果: array([ True, True, False]) 注:指2,3True,4False

np.all(np.in1d([2, 3], [2, 9, 3]))

输出结果: array([ True, True])

是否全是all

np.all(np.in1d([2, 3, 4], [2, 9, 3])) # 判断组合1是否包含在组合2中

输出结果: False

np.all(np.in1d([2, 3], [2, 9, 3]))

输出结果: True

优化前后的效率对比

总结
优化算法是在这个项目上时间花费最多的工作(没有之一)。4月12日接单,10天左右出了第1稿,虽能运行,但回头看存在两个问题:一是有bug需要修正,二是运行效率不高(4500万行数据,执行需要1小时21分钟,如果只是在这个版本上debug需要增加判断条件,效率只会更低);后20多天是在不断的优化算法的同时对bug进行修正,最后版本执行相同数据只需要不足30分钟,效率提高了一倍多。回顾来看,虽然调优花费的时间多,但是每一个尝试不论成功还是失败都是一次宝贵的经验积累。

目录
相关文章
|
7天前
|
计算机视觉 Python
如何使用Python将TS文件转换为MP4
本文介绍了如何使用Python和FFmpeg将TS文件转换为MP4文件。首先需要安装Python和FFmpeg,然后通过`subprocess`模块调用FFmpeg命令,实现文件格式的转换。代码示例展示了具体的操作步骤,包括检查文件存在性、构建FFmpeg命令和执行转换过程。
30 7
|
2月前
|
安全 Linux 数据安全/隐私保护
python知识点100篇系列(15)-加密python源代码为pyd文件
【10月更文挑战第5天】为了保护Python源码不被查看,可将其编译成二进制文件(Windows下为.pyd,Linux下为.so)。以Python3.8为例,通过Cython工具,先写好Python代码并加入`# cython: language_level=3`指令,安装easycython库后,使用`easycython *.py`命令编译源文件,最终生成.pyd文件供直接导入使用。
python知识点100篇系列(15)-加密python源代码为pyd文件
|
2月前
|
数据处理 Python
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
这篇文章介绍了如何使用Python读取Excel文件中的数据,处理后将其保存为txt、xlsx和csv格式的文件。
86 3
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
|
1月前
|
开发者 Python
Python中__init__.py文件的作用
`__init__.py`文件在Python包管理中扮演着重要角色,通过标识目录为包、初始化包、控制导入行为、支持递归包结构以及定义包的命名空间,`__init__.py`文件为组织和管理Python代码提供了强大支持。理解并正确使用 `__init__.py`文件,可以帮助开发者更好地组织代码,提高代码的可维护性和可读性。
46 2
|
2月前
|
Linux 区块链 Python
Python实用记录(十三):python脚本打包exe文件并运行
这篇文章介绍了如何使用PyInstaller将Python脚本打包成可执行文件(exe),并提供了详细的步骤和注意事项。
81 1
Python实用记录(十三):python脚本打包exe文件并运行
|
1月前
|
中间件 Docker Python
【Azure Function】FTP上传了Python Function文件后,无法在门户页面加载函数的问题
通过FTP上传Python Function至Azure云后,出现函数列表无法加载的问题。经排查,发现是由于`requirements.txt`中的依赖包未被正确安装。解决方法为:在本地安装依赖包到`.python_packages/lib/site-packages`目录,再将该目录内容上传至云上的`wwwroot`目录,并重启应用。最终成功加载函数列表。
|
2月前
|
Java Python
> python知识点100篇系列(19)-使用python下载文件的几种方式
【10月更文挑战第7天】本文介绍了使用Python下载文件的五种方法,包括使用requests、wget、线程池、urllib3和asyncio模块。每种方法适用于不同的场景,如单文件下载、多文件并发下载等,提供了丰富的选择。
|
2月前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
2月前
|
JSON 数据格式 Python
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
这篇文章介绍了一个Python脚本,用于统计TXT或JSON文件中特定单词的出现次数。它包含两个函数,分别处理文本和JSON文件,并通过命令行参数接收文件路径、目标单词和文件格式。文章还提供了代码逻辑的解释和示例用法。
53 0
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
|
2月前
|
Python
Python实用记录(四):os模块-去后缀或者改后缀/指定目录下图片或者子目录图片写入txt/csv
本文介绍了如何使用Python的os模块来操作文件,包括更改文件后缀、分割文件路径和后缀、将指定目录下的所有图片写入txt文档,以及将指定目录下所有子目录中的图片写入csv文档,并为每个子目录分配一个标签。
28 1