2017-02-10 19 views
34

Tôi có một số tập tin văn bản, nói 50, mà tôi cần phải đọc vào một dataframe lớn. Hiện tại, tôi đang sử dụng các bước sau.Làm cách nào để tăng tốc độ đọc nhiều tệp và đưa dữ liệu vào một khung dữ liệu?

  1. Đọc mọi tệp và kiểm tra xem nhãn là gì. Thông tin tôi cần thường được chứa trong một vài dòng đầu tiên. Các nhãn giống nhau chỉ lặp lại cho phần còn lại của tệp, với các loại dữ liệu khác nhau được liệt kê dựa trên chúng mỗi lần.
  2. Tạo một khung dữ liệu với các nhãn đó.
  3. Đọc lại tệp và điền vào khung dữ liệu với các giá trị.
  4. Kết hợp khung dữ liệu đó với một khung dữ liệu chính.

Điều này hoạt động khá tốt đối với các tệp có kích thước 100 KB - một vài phút, nhưng ở mức 50 MB, chỉ mất vài giờ và không thực tế.

Tôi làm cách nào để tối ưu hóa mã của mình? Cụ thể -

  1. Làm cách nào để xác định những chức năng nào đang dùng nhiều thời gian nhất mà tôi cần tối ưu hóa? Có phải đọc tệp không? Nó có phải là văn bản cho khung dữ liệu không? Chương trình của tôi dành thời gian ở đâu?
  2. Tôi có nên xem xét đa luồng hoặc đa xử lý không?
  3. Tôi có thể cải thiện thuật toán không?
    • Có lẽ đọc toàn bộ tập tin trong một đi vào một danh sách, chứ không phải là từng dòng,
    • dữ liệu Parse trong khối/toàn bộ tập tin, chứ không phải là từng dòng,
    • dữ liệu Gán cho dataframe trong khối/một đi, thay vì hàng theo hàng.
  4. Có bất kỳ điều gì khác mà tôi có thể thực hiện để làm cho mã của mình chạy nhanh hơn không?

Đây là mã ví dụ. Mã của riêng tôi phức tạp hơn một chút, vì các tệp văn bản phức tạp hơn nên tôi phải sử dụng khoảng 10 cụm từ thông dụng và nhiều vòng lặp để đọc dữ liệu và phân bổ nó đến đúng vị trí trong mảng bên phải. Để giữ cho MWE đơn giản, tôi đã không sử dụng các nhãn lặp lại trong các tệp đầu vào cho MWE, vì vậy nó muốn tôi đọc tệp hai lần mà không có lý do gì. Tôi hy vọng điều đó đúng!

import re 
import pandas as pd 

df = pd.DataFrame() 
paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"] 
reg_ex = re.compile('^(.+) (.+)\n') 
# read all files to determine what indices are available 
for path in paths: 
    file_obj = open(path, 'r') 
    print file_obj.readlines() 

['a 1\n', 'b 2\n', 'end'] 
['c 3\n', 'd 4\n', 'end'] 

indices = [] 
for path in paths: 
    index = [] 
    with open(path, 'r') as file_obj: 
     line = True 
     while line: 
      try: 
       line = file_obj.readline() 
       match = reg_ex.match(line) 
       index += match.group(1) 
      except AttributeError: 
       pass 
    indices.append(index) 
# read files again and put data into a master dataframe 
for path, index in zip(paths, indices): 
    subset_df = pd.DataFrame(index=index, columns=["Number"]) 
    with open(path, 'r') as file_obj: 
     line = True 
     while line: 
      try: 
       line = file_obj.readline() 
       match = reg_ex.match(line) 
       subset_df.loc[[match.group(1)]] = match.group(2) 
      except AttributeError: 
       pass 
    df = pd.concat([df, subset_df]).sort_index() 
print df 

    Number 
a  1 
b  2 
c  3 
d  4 

tập tin đầu vào của tôi:

test1.txt

a 1 
b 2 
end 

test2.txt

c 3 
d 4 
end 
+2

Có thể có đĩa nhanh hơn :) –

+3

Trong thời gian chờ đợi, hãy tìm kiếm một trình thu thập thông tin bằng Python tốt. Đó là lớp chung của công cụ sẽ cho bạn biết phần nào của chương trình là nút cổ chai. –

+1

Bạn có thể không đọc toàn bộ 50 tệp trong dataframe và sau đó chạy các thao tác dựa trên regex không? Điều đó sẽ nhanh chóng như các hoạt động lọc trên gấu trúc rất nhanh .... – vks

Trả lời

0

Nó chỉ ra rằng tạo một DataFrame trống đầu tiên, tìm kiếm chỉ mục để tìm đúng nơi cho một hàng dữ liệu, và sau đó cập nhật chỉ một hàng của DataFrame là một quá trình tốn kém thời gian ngu ngốc. Cách nhanh hơn để thực hiện điều này là đọc nội dung của tệp đầu vào vào một cấu trúc dữ liệu nguyên thủy như danh sách danh sách hoặc danh sách các dicts, sau đó chuyển đổi nó thành một DataFrame.

Sử dụng danh sách khi tất cả dữ liệu bạn đang đọc nằm trong cùng một cột. Nếu không, hãy sử dụng dicts để nói rõ ràng từng cột dữ liệu nào nên đi đến.

Cập nhật ngày 18 tháng 1: Điều này được liên kết với How to parse complex text files using Python? Tôi cũng đã viết blog article explaining how to parse complex files to beginners.

1

Bạn có thể nhập các mô hình đa xử lý và sử dụng một hồ bơi của các quá trình lao động để mở nhiều tệp dưới dạng đối tượng tệp đồng thời, tăng tốc phần tải mã của bạn.Để kiểm tra thời gian, hoặc là nhập khẩu các chức năng datetime và sử dụng đoạn mã sau:

import datetime 
start=datetime.datetime.now() 

#part of your code goes here 

execTime1=datetime.datetime.now() 
print(execTime1-start) 

#the next part of your code goes here 

execTime2=datetime.datetime.now() 
print(execTime2-execTime1) 

Theo như đọc từng tập tin một lần duy nhất, hãy xem xét sử dụng một kịch bản đa để xây dựng một danh sách các dòng trong mỗi tập tin, vì vậy bạn có thể kiểm tra cho một trận đấu không có thao tác nhập/xuất tệp.

1

Thứ nhất, sử dụng một hồ sơ cho kịch bản của bạn (see this question). Phân tích một cách chính xác mà một phần là tốn thời gian hơn. Xem nếu bạn có thể tối ưu hóa nó.

Thứ hai, tôi cảm thấy rằng các I/O tập tin operation- đọc có khả năng nhất Nó có thể được tối ưu hóa bằng cách sử dụng đồng thời phương pháp tiếp cận.Tôi sẽ đề nghị đọc các tập tin đồng thời và tạo khung dữ liệu.Mỗi chủ đề có thể đẩy khung dữ liệu mới được tạo ra cho một hàng đợi.Một hàng đợi giám sát chủ đề chính có thể lấy khung dữ liệu từ hàng đợi và hợp nhất nó với khung dữ liệu chính

Hy vọng điều này sẽ giúp ích cho bạn.

1

1 tạo một mẫu đầu ra cho các tệp (như khung dữ liệu kết quả phải có cột A, BC)

2 đọc từng tệp, chuyển nó thành mẫu đầu ra (được thiết lập ở bước 1) và lưu tệp như temp_idxx.csv, điều này có thể được thực hiện song song :)

3 concatenate những tập tin này temp_idxx.csv vào một tập tin lớn và xóa temps

ưu của thủ tục này là nó có thể chạy song song, và nó sẽ không ăn tất cả các bộ nhớ khuyết điểm đang tạo định dạng đầu ra và gắn bó với nó và sử dụng dung lượng đĩa

1

Đọc các tệp trực tiếp vào một khung dữ liệu gấu trúc bằng cách sử dụng pd.read_csv. Để tạo tập hợp con của bạn_df. Sử dụng các phương thức như trình bỏ qua để bỏ qua các dòng ở cuối tệp mà bạn biết bạn sẽ không cần. Có rất nhiều phương thức có sẵn có thể thay thế một số hàm lặp regex mà bạn đang sử dụng, chẳng hạn như error_bad_lines và skip_blank_lines.

Sau đó, sử dụng các công cụ được cung cấp bởi gấu trúc để xóa dữ liệu không cần thiết.

Điều này sẽ cho phép bạn đọc mở và đọc tệp chỉ một lần.

12

Trước khi kéo búa đa xử lý, bước đầu tiên của bạn phải là làm một số hồ sơ. Sử dụng cProfile để xem nhanh để xác định các chức năng nào đang mất nhiều thời gian. Thật không may nếu các dòng của bạn là tất cả trong một cuộc gọi chức năng duy nhất, chúng sẽ hiển thị như là các cuộc gọi thư viện. line_profiler là tốt hơn nhưng mất nhiều thời gian thiết lập hơn.

LƯU Ý. Nếu sử dụng ipython, bạn có thể sử dụng% timeit (lệnh ma thuật cho mô-đun timeit) và% prun (lệnh ma thuật cho mô-đun hồ sơ) cho cả thời gian báo cáo cũng như chức năng của bạn. Tìm kiếm trên google sẽ hiển thị một số hướng dẫn.

Pandas là một thư viện tuyệt vời, nhưng tôi đã từng là nạn nhân thường xuyên sử dụng nó với kết quả tồi tệ. Đặc biệt, hãy cảnh giác với các hoạt động nối thêm()/concat(). Đó có thể là nút cổ chai của bạn nhưng bạn nên hồ sơ để chắc chắn. Thông thường, các thao tác numpy.vstack() và numpy.hstack() nhanh hơn nếu bạn không cần thực hiện căn chỉnh chỉ mục/cột. Trong trường hợp của bạn có vẻ như bạn có thể nhận được bằng Series hoặc 1-D numpy ndarrays mà có thể tiết kiệm thời gian.

BTW, một khối try trong python chậm hơn nhiều so với kiểm tra tình trạng không hợp lệ, vì vậy hãy chắc chắn bạn hoàn toàn cần nó khi dán vào vòng lặp cho mỗi dòng.Đây có lẽ là người hoài thời gian khác; Tôi tưởng tượng bạn mắc kẹt khối thử để kiểm tra AttributeError trong trường hợp match.group (1) thất bại. Tôi sẽ kiểm tra cho một trận đấu hợp lệ đầu tiên.

Ngay cả những sửa đổi nhỏ này cũng đủ để chương trình của bạn chạy nhanh hơn đáng kể trước khi thử bất kỳ thứ gì quyết liệt như xử lý đa. Những thư viện Python là tuyệt vời nhưng mang lại một bộ thách thức mới để giải quyết.

+1

Khá hiển nhiên khi nhìn vào tập lệnh của mình đọc một dòng tệp 50MB theo dòng là nơi xảy ra hiện tượng nghẽn cổ chai. Ngay cả khi thực hiện một pandas.read_excel trên một tệp 50MB sẽ mất một vài phút. – Nemo

2

chung cân nhắc python:

Trước hết về đo lường thời gian bạn có thể sử dụng một đoạn ví dụ:

from time import time, sleep 


class Timer(object): 
    def __init__(self): 
     self.last = time() 


    def __call__(self): 
     old = self.last 
     self.last = time() 
     return self.last - old 

    @property 
    def elapsed(self): 
     return time() - self.last 



timer = Timer() 

sleep(2) 
print timer.elapsed 
print timer() 
sleep(1) 
print timer() 

Sau đó, bạn có thể điểm chuẩn chạy mã nhiều lần, và kiểm tra sự khác biệt.

Về việc này, tôi bình luận inline:

with open(path, 'r') as file_obj: 
    line = True 
    while line: #iterate on realdines instead. 
     try: 
      line = file_obj.readline() 
      match = reg_ex.match(line) 
      index += match.group(1) 
      #if match: 
      # index.extend(match.group(1)) # or extend 

     except AttributeError: 
      pass 

Bạn trước wat mã không thực sự pythonic, bạn có thể muốn thử/trừ. Sau đó, chỉ thử thực hiện trên các dòng tối thiểu có thể.

Các thông báo tương tự áp dụng cho khối mã thứ hai.

Nếu bạn cần đọc cùng một tệp nhiều lần. bạn có thể lưu trữ chúng trong bộ nhớ RAM bằng cách sử dụng StringIO hoặc dễ dàng hơn giữ một đường dẫn {path: content} mà bạn chỉ đọc một lần.

Regex Python được biết là chậm, dữ liệu của bạn có vẻ khá đơn giản, bạn có thể xem xét sử dụng phương pháp tách và dải trên dòng nhập của mình.

striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

tôi khuyên bạn nên đọc: https://gist.github.com/JeffPaine/6213790 video correspondig là ở đây https://www.youtube.com/watch?v=OSGv2VnC0go

7

Tôi đã sử dụng điều này nhiều lần vì nó là một dễ dàng thực hiện cụ thể của đa xử lý.

import pandas as pd 
from multiprocessing import Pool 

def reader(filename): 
    return pd.read_excel(filename) 

def main(): 
    pool = Pool(4) # number of cores you want to use 
    file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...] 
    df_list = pool.map(reader, file_list) #creates a list of the loaded df's 
    df = pd.concat(df_list) # concatenates all the df's into a single df 

if __name__ == '__main__': 
    main() 

Sử dụng điều này bạn sẽ có thể tăng đáng kể tốc độ của chương trình mà không làm việc quá nhiều. Nếu bạn không biết có bao nhiêu bộ vi xử lý bạn có, bạn có thể kiểm tra bằng cách kéo lên shell của bạn và gõ

echo %NUMBER_OF_PROCESSORS% 

EDIT: Để thực hiện hoạt động này thậm chí còn nhanh hơn, hãy xem xét việc thay đổi tập tin của bạn để CSV và sử dụng gấu trúc hoạt pandas.read_csv

+0

Mô-đun CSV gốc Python cho phép chỉ định '''' làm dấu phân cách. –

1

Mã của bạn không làm những gì bạn mô tả.

Câu hỏi: 1. Đọc mọi tệp và kiểm tra nhãn là gì. Thông tin tôi cần thường được chứa trong một vài dòng đầu tiên.

Nhưng bạn đọc các tập tin toàn, không chỉ là một vài dòng. Kết quả này trong việc đọc các tệp hai lần!

Câu hỏi: 2. Đọc lại tệp và điền vào khung dữ liệu có giá trị.

Bạn ghi đè df['a'|'b'|'c'|'d'] trong vòng lặp một lần nữa và một lần nữa, đó là vô ích
I belive đây không phải là những gì bạn muốn.
Điều này phù hợp với Dữ liệu được đưa ra trong Câu hỏi, nhưng không phải nếu bạn phải xử lý các giá trị n.


Proposal với một logic khác nhau:

data = {} 
for path in paths: 
    with open(path, 'r') as file_obj: 
     line = True 
     while line: 
      try: 
       line = file_obj.readline() 
       match = reg_ex.match(line) 
       if match.group(1) not in data: 
        data[ match.group(1) ] = [] 

       data[match.group(1)].append(match.group(2)) 
      except AttributeError: 
       pass 

print('data=%s' % data) 
df = pd.DataFrame.from_dict(data, orient='index').sort_index() 
df.rename(index=str, columns={0: "Number"}, inplace=True) 

Output:

data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']} 
<class 'pandas.core.frame.DataFrame'> 
Index: 4 entries, a to d 
Data columns (total 1 columns): 
Number 4 non-null object 
dtypes: object(1) 
memory usage: 32.0+ bytes 
    Number 
a  1 
b  2 
c  3 
d  4 

Time Bảng:

   Code from Q: to_dict_from_dict 
    4 values 0:00:00.033071 0:00:00.022146 
1000 values 0:00:08.267750 0:00:05.536500 
10000 values 0:01:22.677500 0:00:55.365000 

Thử nghiệm với Python: 3.4.2 - gấu trúc: 0.19.2 - tái: 2.2.1

+0

Có, bạn đã đúng. MWE của tôi không tuyệt vời. – bluprince13

+0

Vui lòng mở rộng ** MWE ** – stovfl

+0

Nó bắt đầu trở nên khá phức tạp khi tôi bắt đầu sửa đổi nó cho điều đó. Tôi nghĩ rằng tôi sẽ để nó như nó được, nhưng tôi sẽ làm cho nó rõ ràng hơn trong lời giải thích của tôi rằng tôi đã cố gắng để giữ cho MWE đơn giản. – bluprince13

2

Trước hết, nếu bạn đang đọc các tập tin trong nhiều lần, nó có vẻ như đó sẽ là nút cổ chai. Hãy thử đọc tệp thành đối tượng chuỗi 1 và sau đó sử dụng cStringIO trên đó nhiều lần.

Thứ hai, bạn chưa thực sự hiển thị bất kỳ lý do nào để tạo chỉ mục trước khi đọc trong tất cả các tệp. Ngay cả khi bạn làm thế, tại sao bạn sử dụng Pandas cho IO? Nó có vẻ như bạn có thể xây dựng nó trong cấu trúc dữ liệu python thường xuyên (có thể sử dụng __slots__) và sau đó đặt nó trong dataframe chính. Nếu bạn không cần tập tin X chỉ mục trước khi bạn đọc tập tin Y (như bạn vòng 2 dường như đề nghị), bạn chỉ cần lặp qua các tập tin một lần. Thứ ba, bạn có thể sử dụng đơn giản split/strip trên các chuỗi để tách các khoảng trống tách biệt, hoặc nếu nó phức tạp hơn (có các trích dẫn chuỗi và như vậy) sử dụng mô-đun CSV từ thư viện chuẩn của Python. Cho đến khi bạn chỉ ra cách bạn thực sự xây dựng dữ liệu của mình, thật khó để đề xuất một sửa chữa liên quan đến điều đó.

Những gì bạn đã cho thấy cho đến nay có thể được thực hiện khá nhanh chóng với các đơn giản

for path in paths: 
    data = [] 
    with open(path, 'r') as file_obj: 
     for line in file_obj: 
      try: 
       d1, d2 = line.strip().split() 
      except ValueError: 
       pass 
      data.append(d1, int(d2))) 
    index, values = zip(*data) 
    subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)}) 

Đây là sự khác biệt trong timings khi tôi chạy trên một máy ảo với không gian đĩa không được cấp phát trước (các tập tin được tạo ra là khoảng 24MB trong kích thước):

import pandas as pd 
from random import randint 
from itertools import combinations 
from posix import fsync 


outfile = "indexValueInput" 

for suffix in ('1', '2'): 
    with open(outfile+"_" + suffix, 'w') as f: 
     for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) : 
      val = randint(1, 1000000) 
      print >>f, "%s %d" % (''.join(label), val) 
      if i > 3999999: 
       break 
     print >>f, "end" 
     fsync(f.fileno()) 

def readWithPandas(): 
    data = [] 
    with open(outfile + "_2", 'r') as file_obj: 
     for line in file_obj: 
      try: 
       d1, d2 = str.split(line.strip()) 
      except ValueError: 
       pass 
      data.append((d1, int(d2))) 
    index, values = zip(*data) 
    subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)}) 

def readWithoutPandas(): 
    data = [] 
    with open(outfile+"_1", 'r') as file_obj: 
     for line in file_obj: 
      try: 
       d1, d2 = str.split(line.strip()) 
      except ValueError: 
       pass 
      data.append((d1, int(d2))) 
    index, values = zip(*data) 

def time_func(func, *args): 
    import time 
    print "timing function", str(func.func_name) 
    tStart = time.clock() 
    func(*args) 
    tEnd = time.clock() 
    print "%f seconds " % (tEnd - tStart) 

time_func(readWithoutPandas) 
time_func(readWithPandas) 

giờ kết quả là:

timing function readWithoutPandas 
4.616853 seconds 
timing function readWithPandas 
4.931765 seconds 

Bạn có thể thử các chức năng này với chỉ mục tích lũy của bạn và xem sự khác biệt về thời gian sẽ là gì. Nó gần như chắc chắn rằng sự chậm lại đến từ nhiều lần đọc đĩa. Và kể từ khi Pandas sẽ không có thời gian để xây dựng dataframe của bạn từ một từ điển, bạn nên tìm cách xây dựng chỉ mục của bạn trong Python tinh khiết trước khi chuyển dữ liệu đến Pandas. Nhưng làm cả dữ liệu đọc và xây dựng chỉ mục trong 1 đĩa đọc.

Tôi đoán một điều khác nữa là nếu bạn in từ bên trong mã của mình, bạn sẽ mất nhiều thời gian. Thời gian cần để viết văn bản thuần túy cho một người lùn lùn thời gian cần để đọc/ghi vào đĩa.

Các vấn đề liên quan