Выделите точечный график в определенные ячейки - PullRequest
0 голосов
/ 02 октября 2018

У меня есть scatter plot, который сортируется в 4 Bins.Они разделены двумя arcs и line посередине (см. Рисунок ниже).

Существует небольшая проблема с двумя arcs.Если X-Coordiante больше, чем ang2, оно не будет отнесено к правильному Bin.(См. Рисунок ниже)

import math
import matplotlib.pyplot as plt
import matplotlib as mpl

X = [24,15,71,72,6,13,77,52,52,62,46,43,31,35,41]  
Y = [94,61,76,83,69,86,78,57,45,94,82,74,56,70,94]      

fig, ax = plt.subplots()
ax.set_xlim(-100,100)
ax.set_ylim(-40,140)
ax.grid(False)

plt.scatter(X,Y)

#middle line
BIN_23_X = 0 
#two arcs
ang1 = -60, 60
ang2 = 60, 60
angle = math.degrees(math.acos(2/9.15))
E_xy = 0,60

Halfway = mpl.lines.Line2D((BIN_23_X,BIN_23_X), (0,125), color = 'white', lw = 1.5, alpha = 0.8, zorder = 1)
arc1 = mpl.patches.Arc(ang1, 70, 110, angle = 0, theta2 = angle, theta1 = 360-angle, color = 'white', lw = 2)
arc2 = mpl.patches.Arc(ang2, 70, 110, angle = 0, theta2 = 180+angle, theta1 = 180-angle, color = 'white', lw = 2)
Oval = mpl.patches.Ellipse(E_xy, 160, 130, lw = 3, edgecolor = 'black', color = 'white', alpha = 0.2)

ax.add_line(Halfway)
ax.add_patch(arc1)
ax.add_patch(arc2)
ax.add_patch(Oval)

#Sorting the coordinates into bins   
def get_nearest_arc_vert(x, y, arc_vertices):
err = (arc_vertices[:,0] - x)**2 + (arc_vertices[:,1] - y)**2
nearest = (arc_vertices[err == min(err)])[0]
return nearest

arc1v = ax.transData.inverted().transform(arc1.get_verts())
arc2v = ax.transData.inverted().transform(arc2.get_verts())

def classify_pointset(vx, vy):
    bins = {(k+1):[] for k in range(4)}
    for (x,y) in zip(vx, vy):
        nx1, ny1 = get_nearest_arc_vert(x, y, arc1v)
        nx2, ny2 = get_nearest_arc_vert(x, y, arc2v)

        if x < nx1:                         
            bins[1].append((x,y))
        elif x > nx2:                      
            bins[4].append((x,y))
        else:
            if x < BIN_23_X:               
                bins[2].append((x,y))
            else:                          
               bins[3].append((x,y))
    return bins

#Bins Output
bins_red  = classify_pointset(X,Y)

all_points = [None] * 5
for bin_key in [1,2,3,4]:
    all_points[bin_key] = bins_red[bin_key] 

Вывод:

[[], [], [(24, 94), (15, 61), (71, 76), (72, 83), (6, 69), (13, 86), (77, 78), (62, 94)], [(52, 57), (52, 45), (46, 82), (43, 74), (31, 56), (35, 70), (41, 94)]]

Это не совсем верно.Глядя на figure output ниже, 4 coordinates в Bin 3 и 11 в Bin 4.Но 8 относятся к Bin 3, а 7 относятся к Bin 4.

Я думаю, что проблема в blue coordinates.В частности, когда X-Coordinate больше ang2, что составляет 60.Если я изменю их на значение меньше 60, они будут исправлены на Bin 3.

Я не уверен, что мне следует расширить , чтобы arcs было больше 60 или если код можно улучшить?

Обратите внимание, это только для Bin 4 и ang2.Эта проблема возникнет для Bin 1 и ang1.То есть, если X-Cooridnate равен меньше 60 , он не будет приписан к Bin 1

Предполагаемый результат:

[[], [], [(24, 94), (15, 61), (6, 69), (13, 86)], [(71, 76), (72, 83), (52, 57), (52, 45), (46, 82), (43, 74), (31, 56), (35, 70), (41, 94), (77, 78), (62, 94)]]

enter image description here

Примечание: Предполагаемый выход является предпочтительным.В примере используется один row входных данных.Тем не менее, мой набор данных намного больше.Если мы используем множество rows, вывод должен быть построчно.например,

#Numerous rows
X = np.random.randint(50, size=(100, 10))
Y = np.random.randint(80, size=(100, 10)) 

Out:

Row 0 = [(x,y)],[(x,y)],[(x,y)],[(x,y)]
Row 1 = [(x,y)],[(x,y)],[(x,y)],[(x,y)]
Row 2 = [(x,y)],[(x,y)],[(x,y)],[(x,y)]
etc

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Это версия, где я сортирую ее по эллипсам.Поскольку ОП использует простые геометрические фигуры, можно проверить это с помощью простой формулы, то есть не «запрашивая» патч.Я обобщил это для n дуг с небольшим недостатком, заключающимся в том, что нумерация бинов не слева направо, но об этом можно позаботиться в другом месте.Вывод имеет тип

[ [ [x,y], [x,y],...], ... ] 

, то есть список x, y для каждой ячейки.Нумерация здесь от -3 до 3, хотя 0 находится снаружи.

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

def in_ellipse( xy, x0y0ab):
    x, y = xy
    x0, y0 = x0y0ab[0]
    a = x0y0ab[1]/2.  ## as the list of ellipses takes width and not semi axis
    b = x0y0ab[2]/2.
    return ( x - x0 )**2 / a**2+ ( y - y0 )**2 / b**2 < 1

def sort_into_bins( xy, mainE, eList ):
    binCntr = 0
    xyA = (np.abs(xy[0]),xy[1]) ## all positive
    if in_ellipse( xyA, mainE ):
        binCntr +=1
        for ell in eList:
            if in_ellipse( xyA, ell ):
                break
            binCntr +=1
    binCntr=np.copysign( binCntr, xy[0] )
    return int( binCntr )

X = 200 * np.random.random(150) - 100
Y = 140 * np.random.random(150) - 70 + 60

fig, ax = plt.subplots()
ax.set_xlim(-100,100)
ax.set_ylim(-40,140)
ax.grid(False)


BIN_23_X = 0 
mainEllipse = [ np.array([0, 60]), 160, 130 ]
allEllipses = [ [ np.array([60,60]), 70., 110. ], [ np.array([60,60]), 100, 160 ]  ]

Halfway = mpl.lines.Line2D((BIN_23_X,BIN_23_X), (0,125), color = '#808080', lw = 1.5, alpha = 0.8, zorder = 1)
Oval = mpl.patches.Ellipse( mainEllipse[0], mainEllipse[1], mainEllipse[2], lw = 3, edgecolor = '#808080', facecolor = '#808080', alpha = 0.2)
ax.add_patch(Oval)
ax.add_line(Halfway)

for ell in allEllipses:
    arc =  mpl.patches.Arc( ell[0] , ell[1], ell[2], angle = 0,  color = '#808080', lw = 2, linestyle=':')
    ax.add_patch( arc )
    arc =  mpl.patches.Arc( ell[0] * np.array([ -1, 1 ]), ell[1], ell[2], angle = 0,  color = '#808080', lw = 2, linestyle=':')
    ax.add_patch( arc )

binDict = dict()
for x,y in zip(X,Y):
    binDict[( x,y)]=sort_into_bins( (x,y), mainEllipse, allEllipses )

rowEval=[]
for s in range(-3,4):
    rowEval+=[[]]
for key, val in binDict.iteritems():
    rowEval[ val + 3 ]+=[key]

for s in range(-3,4):
    plt.scatter( *zip( *rowEval[ s + 3 ] ) )

plt.show()

показывает

test data

Обратите внимание, что я использовалфакт симметрии относительно х = 0.Если эллипсы смещены относительно x, код должен быть немного изменен.Также обратите внимание, что порядок, в котором предоставляются эллипсы, имеет значение!

0 голосов
/ 02 октября 2018

Патчи имеют тест на наличие точек или нет: contains_point и даже для массивов точек: contains_points

Просто для игры у меня есть фрагмент кода для вас, который вы можете добавить междучасть, в которой вы добавляете свои патчи и кодовый блок #Sorting the coordinates into bins.

Он добавляет два дополнительных (прозрачных) эллипса для расчета, будут ли дуги содержать точки, если они были полностью закрытыми эллипсами.Тогда ваш расчет бина - это просто логическая комбинация тестов, если точка принадлежит большому овалу, левому или правому эллипсу или имеет положительную или отрицательную x-координату.

ov1 = mpl.patches.Ellipse(ang1, 70, 110, alpha=0)
ov2 = mpl.patches.Ellipse(ang2, 70, 110, alpha=0)
ax.add_patch(ov1)
ax.add_patch(ov2)

for px, py in zip(X, Y):
    in_oval = Oval.contains_point(ax.transData.transform(([px, py])), 0)
    in_left = ov1.contains_point(ax.transData.transform(([px, py])), 0)
    in_right = ov2.contains_point(ax.transData.transform(([px, py])), 0)
    on_left = px < 0
    on_right = px > 0
    if in_oval:
        if in_left:
            n_bin = 1
        elif in_right:
            n_bin = 4
        elif on_left:
            n_bin = 2
        elif on_right:
            n_bin = 3
        else:
            n_bin = -1
    else:
        n_bin = -1
    print('({:>2}/{:>2}) is {}'.format(px, py, 'in Bin ' +str(n_bin) if n_bin>0 else 'outside'))

Вывод:

(24/94) is in Bin 3
(15/61) is in Bin 3
(71/76) is in Bin 4
(72/83) is in Bin 4
( 6/69) is in Bin 3
(13/86) is in Bin 3
(77/78) is outside
(52/57) is in Bin 4
(52/45) is in Bin 4
(62/94) is in Bin 4
(46/82) is in Bin 4
(43/74) is in Bin 4
(31/56) is in Bin 4
(35/70) is in Bin 4
(41/94) is in Bin 4

Обратите внимание, что вы все равно должны решить, как определять ячейки, когда точки имеют координату x = 0 - в тот момент, когда они равны снаружи, так как on_left и on_right оба не чувствуют ответственности за них...

PS: Благодаря @ImportanceOfBeingErnest за подсказку к необходимому преобразованию: https://stackoverflow.com/a/49112347/8300135

Примечание: для всех следующих РЕДАКТИРОВАНИЙ вам потребуется import numpy as np
РЕДАКТИРОВАТЬ: Функция для подсчета распределения бина на X, Y массив ввода:

def bin_counts(X, Y):
    bc = dict()
    E = Oval.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    E_l = ov1.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    E_r = ov2.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    L = np.array(X) < 0
    R = np.array(X) > 0
    bc[1] = np.sum(E & E_l)
    bc[2] = np.sum(E & L & ~E_l)
    bc[3] = np.sum(E & R & ~E_r)
    bc[4] = np.sum(E & E_r)
    return bc

приведет к этому результату:

bin_counts(X, Y)
Out: {1: 0, 2: 0, 3: 4, 4: 10}

EDIT2: множество строк в двух 2D-массивах для X и Y:

np.random.seed(42)
X = np.random.randint(-80, 80, size=(100, 10))
Y = np.random.randint(0, 120, size=(100, 10))

с циклом по всем строкам:

for xr, yr in zip(X, Y):
    print(bin_counts(xr, yr))

результат:

{1: 1, 2: 2, 3: 6, 4: 0}
{1: 1, 2: 0, 3: 4, 4: 2}
{1: 5, 2: 2, 3: 1, 4: 1}
...
{1: 3, 2: 2, 3: 2, 4: 0}
{1: 2, 2: 4, 3: 1, 4: 1}
{1: 1, 2: 1, 3: 6, 4: 2}

EDIT3: для возврата не количества точек в каждом бине, а массива с четырьмя массивами, содержащимиng x, y-координаты точек в каждом бине, используйте следующее:

X = [24,15,71,72,6,13,77,52,52,62,46,43,31,35,41]  
Y = [94,61,76,83,69,86,78,57,45,94,82,74,56,70,94]      

def bin_points(X, Y):
    X = np.array(X)
    Y = np.array(Y)
    E = Oval.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    E_l = ov1.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    E_r = ov2.contains_points(ax.transData.transform(np.array([X, Y]).T), 0)
    L = X < 0
    R = X > 0
    bp1 = np.array([X[E & E_l], Y[E & E_l]]).T
    bp2 = np.array([X[E & L & ~E_l], Y[E & L & ~E_l]]).T
    bp3 = np.array([X[E & R & ~E_r], Y[E & R & ~E_r]]).T
    bp4 = np.array([X[E & E_r], Y[E & E_r]]).T
    return [bp1, bp2, bp3, bp4]

print(bin_points(X, Y))
[array([], shape=(0, 2), dtype=int32), array([], shape=(0, 2), dtype=int32), array([[24, 94],
       [15, 61],
       [ 6, 69],
       [13, 86]]), array([[71, 76],
       [72, 83],
       [52, 57],
       [52, 45],
       [62, 94],
       [46, 82],
       [43, 74],
       [31, 56],
       [35, 70],
       [41, 94]])]

... и снова, чтобы применить это к большим 2D-массивам, просто итерируйте по ним:

np.random.seed(42)
X = np.random.randint(-100, 100, size=(100, 10))
Y = np.random.randint(-40, 140, size=(100, 10))

bincol = ['r', 'g', 'b', 'y', 'k']

for xr, yr in zip(X, Y):
    for i, binned_points in enumerate(bin_points(xr, yr)):
        ax.scatter(*binned_points.T, c=bincol[i], marker='o' if i<4 else 'x')

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...