У меня есть следующий код, который отображает ydata
против xdata
, который должен быть кругом. График состоит из двух подграфиков — линейного графика с маркерами и диаграммы рассеяния.
import matplotlib.pyplot as plt
xdata = [-1.9987069285852805, -1.955030386765729, -1.955030386765729, -1.8259096357678795, -1.8259096357678795, -1.6169878720004491, -1.6169878720004491, -1.3373959790579202, -1.3373959790579202, -0.9993534642926399, -0.9993534642926399, -0.6176344077078071, -0.6176344077078071, -0.20892176376743077, -0.20892176376743077, 0.20892176376743032, 0.20892176376743032, 0.6176344077078065, 0.6176344077078065, 0.999353464292642, 0.999353464292642, 1.3373959790579217, 1.3373959790579217, 1.6169878720004487, 1.6169878720004487, 1.8259096357678786, 1.8259096357678786, 1.9550303867657255, 1.9550303867657255, 1.9987069285852832]
ydata = (0.0, -0.038801795445724575, 0.038801795445724575, -0.07590776623879933, 0.07590776623879933, -0.10969620340136318, 0.10969620340136318, -0.13869039009450249, 0.13869039009450249, -0.16162314123018345, 0.16162314123018345, -0.1774921855402276, 0.1774921855402276, -0.18560396964016201, 0.18560396964016201, -0.185603969640162, 0.185603969640162, -0.17749218554022747, 0.17749218554022747, -0.16162314123018337, 0.16162314123018337, -0.13869039009450224, 0.13869039009450224, -0.10969620340136294, 0.10969620340136294, -0.0759077662387991, 0.0759077662387991, -0.038801795445725006, 0.038801795445725006, 0.0)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,6))
fig.suptitle('Plot comparison: line vs scatter'+ 3*'\n')
fig.subplots_adjust(wspace=1, hspace=3)
fig.supxlabel('x')
fig.supylabel('y')
ax1.plot(xdata, ydata, 'o-', c='blue')
ax1.set_title('Line-point plot', c='blue')
for i in range(len(xdata)):
ax2.scatter(xdata, ydata, c='orange')
ax2.set_title('Scatter plot', c='orange')
plt.savefig('line_vs_scatter_plot.png')
plt.show()
Выход:
Из выходных данных видно, что линейный график не соединяет точки (или точки). Можем ли мы каким-либо образом изменить порядок данных x или y, чтобы решить проблему? Или сделать что-то еще?
Точки были созданы путем решения комплексного матричного уравнения (проблемы собственных значений).
Вы можете сортировать точки по часовой стрелке, здесь — реализация. В вашем случае вам придется заархивировать списки x и y:
origin = [0, 0]
refvec = [0, 1]
x2, y2 = zip(*sorted(zip(xdata, ydata), key=clockwiseangle_and_distance))
plt.plot(x2, y2, 'o-', c='blue')
Выход:
по часовой стрелке_and_distance функция @MSeifert
import math
def clockwiseangle_and_distance(point):
# Vector between point and the origin: v = p - o
vector = [point[0]-origin[0], point[1]-origin[1]]
# Length of vector: ||v||
lenvector = math.hypot(vector[0], vector[1])
# If length is zero there is no angle
if lenvector == 0:
return -math.pi, 0
# Normalize vector: v/||v||
normalized = [vector[0]/lenvector, vector[1]/lenvector]
dotprod = normalized[0]*refvec[0] + normalized[1]*refvec[1] # x1*x2 + y1*y2
diffprod = refvec[1]*normalized[0] - refvec[0]*normalized[1] # x1*y2 - y1*x2
angle = math.atan2(diffprod, dotprod)
# Negative angles represent counter-clockwise angles so we need to subtract them
# from 2*pi (360 degrees)
if angle < 0:
return 2*math.pi+angle, lenvector
# I return first the angle because that's the primary sorting criterium
# but if two vectors have the same angle then the shorter distance should come first.
return angle, lenvector
Если координаты xy имеют последовательный зигзагообразный рисунок (нечетный/четный), вы можете сделать:
def unzigzag(data):
data = list(data) # just in case
return data[::2] + data[::-2] + [data[0]]
ax1.plot(*map(unzigzag, [xdata, ydata]), "bo-")
NB: Этот график аннотирован физическим положением (начиная с 1) x/y в их списках.
Если нет, то одним из вариантов будет использование shapely :
from shapely import MultiPoint
from shapely.geometry.polygon import orient
def unzigzag(x, y, ori=-1): # -1: clock-wise
p = MultiPoint(list(zip(x, y))).convex_hull
return list(orient(p, ori).boundary.coords)
ax2.plot(*zip(*unzigzag(xdata, ydata)), "-bo") # the ori has no effect
Анимация для выделения ориентации:
Спасибо. Второй ответ работает. Первый ответ дает следующую ошибку: return data[1::2] + data[-2::-2] + [data[1]] ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~ TypeError: can only concatenate tuple (not "list") to tuple
. Кстати, что означает эта линия? Сначала расположите все нечетные элементы по часовой стрелке, затем добавьте четные против часовой стрелки, а затем снова добавьте первый нечетный элемент, чтобы завершить круг?
Вы можете добавить data = list(data)
просто для того, чтобы убедиться, что у нас есть списки перед объединением (см. обновление). Что касается вашего вопроса, да, это идея первого подхода/unzigzag
.
Спасибо, я понял, что мой xdata
— это список, а мой ydata
— кортеж. Следовательно, объединение выдает ошибку из-за этого несоответствия. У меня возникла идея: если мы разделим сектор на четный и нечетный, мы сможем избежать соединения двух последовательных точек в исходном порядке. Однако почему разделение происходит по часовой стрелке и против часовой стрелки?
Хорошие графики и объяснения @Timeless ;)
Ах! Я вижу сейчас. Поскольку оба идут по часовой стрелке, соединение не теряет согласованности. И всё же есть некоторые сомнения: (1) Где data[0]
? Я думаю, data[-2::-2]
начинается с data[28] и заканчивается на data[0]. Если я прав, ваша вторая стрелка должна начинаться с индекса 28, а не с 30. Индекса 30 вообще нет, если вы следуете индексации на основе Python. (2) Не является ли data[1]
перекрытием двух точек (одна и та же точка нанесена дважды)?
@hbaromega, ты был прав, настаивая, исправлено;). Для индексов на графике я использую 1-индексацию.
@Timeless, спасибо за новости. Не могли бы вы соответствующим образом обновить стрелки на графике, просто чтобы они соответствовали индексации Python в коде?
Вы можете начать с одной точки и сортировать их по расстоянию по мере последовательного добавления точек, но это будет довольно медленно. Если это разовая вещь и вам не нужно делать это для многих очков, тогда все в порядке. Как были начислены баллы?