Построение функции двух переменных на основе данных из файла

#python #matplotlib

Вопрос:

Предположим, значения функции записываются строка за строкой в файл в форме x y f(x, y) , и я зачитываю этот файл в список списков [ [x1, y1, f(x1, y1)], ..., [xN, yN, f(xN, yN)] ] :

 with open('data.txt', 'r') as file:
    data = [[float(x) for x in line.split()] for line in file]
 

Вопрос в том, как построить эту функцию.

Мне удалось написать программу (см. Ниже), которая реализует задачу (значения для data взяты в качестве примера), но она выглядит слишком сложной и, как мне кажется, есть более элегантное решение.

 import numpy as np
import matplotlib.pyplot as plt

data = [[0, 0, 1], [0, 1, 1], [1, 0, 2], [1, 1, 3]]
x, y, _ = np.array(data).T.tolist()

x = list(set(x))
y = list(set(y))

def f(x, y):
    for val in data:
        if x == val[0] and y == val[1]:
            return val[2]

X, Y = np.meshgrid(x, y)
Z = [[f(x_, y_) for x_ in x] for y_ in y]

cp = plt.contourf(Y, X, Z)
plt.colorbar(cp)

plt.show()
 

Поэтому я думаю, что правильнее спросить, как решить проблему изящно, элегантно.

Ответ №1:

Я нашел способ значительно ускорить заполнение Z . Решение, конечно, не является элегантным.

 import numpy as np
import matplotlib.pyplot as plt

data = [[0, 0, 1], [0, 1, 1], [1, 0, 2], [1, 1, 3]]
x, y, _ = np.array(data).T.tolist()

x = list(set(x))
y = list(set(y))

X, Y = np.meshgrid(x, y)

#####
Z = np.zeros((len(x), len(y)))

x_ind = {xx[1] : xx[0] for xx in enumerate(x)}
y_ind = {yy[1] : yy[0] for yy in enumerate(y)}

for d in data:
    xx, yy, zz = d
    Z[x_ind[xx], y_ind[yy]] = zz
#####

cp = plt.contourf(Y, X, Z)
plt.colorbar(cp)

plt.show()
 

Я сравниваю эти два подхода с простым кодом:

 import numpy as np
import sys
import time


def f1(data, x, y):
    for val in data:
        if x == val[0] and y == val[1]:
            return val[2]


def init1(data, x, y):
    Z = [[f1(data, x_, y_) for x_ in x] for y_ in y]

    return Z


def init2(data, x, y):
    Z = np.zeros((len(x), len(y)))

    x_ind = {xx[1] : xx[0] for xx in enumerate(x)}
    y_ind = {yy[1] : yy[0] for yy in enumerate(y)}

    for d in data:
        xx, yy, zz = d
        Z[x_ind[xx], y_ind[yy]] = zz

    return Z


def test(n):
    data = []

    x = y = [nn / n for nn in range(n 1)]

    for xx in x:
        for yy in y:
            data.append([xx, yy, 0.])

    t1 = time.time()
    init1(data, x, y)
    dt1 = time.time() - t1

    t2 = time.time()
    init2(data, x, y)
    dt2 = time.time() - t2

    print(f'n = {n:5d} ; t1 = {dt1:10.3f} s ; t2 = {dt2:10.3f} s')


def main():
    n = 10

    if len(sys.argv) > 1:
        try:
            n = int(sys.argv[1])
        except:
            print(f'Can not convert "{sys.argv[1]}" to integer')
            exit(0)

    if n <= 0:
        print(f'n is negative or equal zero')
        exit(0)

    test(n)


if __name__ == '__main__':
    main()
 

Не указывая характеристики машины, на которой была запущена программа, я приведу только результат ее работы для n = 100 и n = 200 :

 $ python test.py 100 ; python test.py 200
n =   100 ; t1 =      1.092 s ; t2 =      0.001 s
n =   200 ; t1 =     19.312 s ; t2 =      0.005 s
 

Конечно, это все еще неэффективный способ. Так, например, для сетки размером 4000 на 4000 потребуется 2 секунды.

Я хочу отметить, что новый метод приемлемо работает с небольшими и средними объемами данных, а время работы matplotlib значительно больше. При больших объемах данных проблемы в первую очередь связаны с matplotlib.

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