#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.
Я думаю, что, хотя решение не является элегантным, оно решает проблему с приемлемой скоростью. Честно говоря, я даже не уверен, что результат можно значительно ускорить.