Сделать pyplot быстрее, чем gnuplot - PullRequest
1 голос
/ 30 сентября 2011

Недавно я решил попробовать matplotlib.pyplot, используя gnuplot для построения научных данных в течение многих лет.Я начал с простого чтения файла данных и построения двух столбцов, как это делал бы gnuplot с plot 'datafile' u 1:2.Требования для моего комфорта:

  • Пропускать строки, начинающиеся с #, и пропускать пустые строки.
  • Разрешить произвольное количество пробелов между и до фактических чисел
  • разрешить произвольное количество столбцов
  • быть быстрым

Теперь следующий кодмое решение проблемы.Однако, по сравнению с gnuplot, это действительно не так быстро.Это немного странно, так как я прочитал, что одним большим преимуществом py (plot / thon) перед gnuplot является его скорость.

import numpy as np
import matplotlib.pyplot as plt
import sys

datafile = sys.argv[1]
data = []
for line in open(datafile,'r'):
    if line and line[0] != '#':
        cols = filter(lambda x: x!='',line.split(' '))
        for index,col in enumerate(cols):
            if len(data) <= index:
                data.append([])
            data[index].append(float(col))

plt.plot(data[0],data[1])
plt.show()

Что бы я сделал для ускорения чтения данных?Я быстро взглянул на модуль csv, но он не казался слишком гибким с комментариями в файлах, и все еще нужно перебирать все строки в файле.

Ответы [ 2 ]

5 голосов
/ 30 сентября 2011

Поскольку у вас установлен matplotlib, у вас также должен быть установлен numpy. numpy.genfromtxt соответствует всем вашим требованиям и должен быть намного быстрее, чем сам анализ файла в цикле Python:

import numpy as np
import matplotlib.pyplot as plt

import textwrap
fname='/tmp/tmp.dat'
with open(fname,'w') as f:
    f.write(textwrap.dedent('''\
        id col1 col2 col3
        2010 1 2 3 4
        # Foo

        2011 5 6 7 8
        # Bar        
        # Baz
        2012 8 7 6 5
        '''))

data = np.genfromtxt(fname, 
                     comments='#',    # skip comment lines
                     dtype = None,    # guess dtype of each column
                     names=True)      # use first line as column names
print(data)
plt.plot(data['id'],data['col2'])
plt.show()
2 голосов
/ 30 сентября 2011

Вам действительно нужно профилировать свой код , чтобы узнать, что является узким местом.

Вот несколько микрооптимизаций:

import numpy as np
import matplotlib.pyplot as plt
import sys

datafile = sys.argv[1]
data = []
# use with to auto-close the file
for line in open(datafile,'r'):
    # line will never be False because it will always have at least a newline
    # maybe you mean line.rstrip()?
    # you can also try line.startswith('#') instead of line[0] != '#'
    if line and line[0] != '#':
        # not sure of the point of this
        # just line.split() will allow any number of spaces
        # if you do need it, use a list comprehension
        # cols = [col for col in line.split(' ') if col]
        # filter on a user-defined function is slow
        cols = filter(lambda x: x!='',line.split(' '))

        for index,col in enumerate(cols):
            # just made data a collections.defaultdict
            # initialized as data = defaultdict(list)
            # and you can skip this 'if' statement entirely
            if len(data) <= index:
                data.append([])
            data[index].append(float(col))

plt.plot(data[0],data[1])
plt.show()

Вы можетесделать что-то вроде:

with open(datafile) as f:
    lines = (line.split() for line in f 
                 if line.rstrip() and not line.startswith('#'))
    data = zip(*[float(col) for col in line for line in lines])

, что даст вам list из tuple с вместо int с ключом dict из list с, но в остальном выглядит идентично.Это можно сделать как однострочник, но я разделил его, чтобы его было легче читать.

...