Asignar el diagtwig de dispersión en contenedores específicos

Tengo un scatter plot que se clasifica en 4 Bins . Estos están separados por dos arcs y una line en el medio (vea la figura a continuación).

Hay un pequeño problema con los dos arcs . Si el X-Coordiante es mayor que el ang2 , no se atribuye a la ang2 correcta. (Por favor vea la figura abajo)

 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  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] 

Salida:

 [[], [], [(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)]] 

Esto no está del todo bien. Mirando la figure output la figure output continuación, 4 coordinates están en la Bin 3 y 11 en la Bin 4 . Pero 8 se atribuyen a Bin 3 y 7 se atribuyen a Bin 4 .

Creo que el problema son las blue coordinates . Específicamente, cuando la X-Coordinate es mayor que ang2 , que es 60 . Si modifico estos para que sean menos de 60 , se corregirán en el Bin 3 .

¿No estoy seguro de si debo extender los arcs a más de 60 o si se puede mejorar el código?

Tenga en cuenta que esto es sólo para Bin 4 y ang2 . El problema ocurrirá para Bin 1 y ang1 . Es decir, si el X-Cooridnate es inferior a 60 , no se atribuirá a Bin 1

Salida prevista:

 [[], [], [(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)]] 

introduzca la descripción de la imagen aquí

Nota: Se prefiere la salida prevista. El ejemplo utiliza una row de datos de entrada. Sin embargo, mi conjunto de datos es mucho más grande. Si usamos numerosas rows la salida debe ser fila por fila. p.ej

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

Afuera:

 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 

Los parches tienen una prueba para contener puntos o no: contains_point e incluso para matrices de puntos: contains_points

Solo para jugar tengo un fragmento de código para ti, que puedes agregar entre la parte en la que estás agregando tus parches y el #Sorting the coordinates into bins bloques de código de #Sorting the coordinates into bins .

Agrega dos elipses adicionales (transparentes) para calcular si los arcos contendrían puntos si fueran elipses completamente cerrados. Entonces su cálculo de bin es solo una combinación booleana de pruebas si un punto pertenece al óvalo grande, la elipsis izquierda o derecha o tiene una coordenada x positiva o negativa.

 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')) 

La salida es:

 (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 

Tenga en cuenta que aún debe decidir cómo definir los bins cuando los puntos tienen x-coord = 0, en el momento en que son iguales a fuera, ya que on_left y on_right no se sienten responsables de ellos …

PD: Gracias a @ImportanceOfBeingErnest por la sugerencia de la transformación necesaria: https://stackoverflow.com/a/49112347/8300135

Nota: para todos los siguientes EDITS necesitará import numpy as np
EDITAR: Función para contar la distribución de bin por entrada de matriz 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 

Llevará a este resultado:

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

EDIT2: muchas filas en dos matrices 2D para X e Y:

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

Recorrido en todas las filas:

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

resultado:

 {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: para devolver no el número de puntos en cada bin, sino una matriz con cuatro arrays que contienen las coordenadas x, y de los puntos en cada bin, use lo siguiente:

 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]])] 

… y otra vez, para aplicar esto a los grandes arreglos 2D, simplemente itere sobre ellos:

 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') 

introduzca la descripción de la imagen aquí

Esta es una versión donde la clasifico en elipses. Como el OP está utilizando formas geométricas simples, se puede probar esto con una fórmula simple, es decir, no “pedir” el parche. Lo generalicé para n arcos con la pequeña desventaja de que la numeración de los contenedores no es de izquierda a derecha, pero esto se puede encargar en otros lugares. La salida es de tipo

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

Es decir, una lista de x, y para cada bins. Sin embargo, la numeración aquí es de -3 a 3, con 0 fuera.

 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() 

demostración

datos de prueba

Tenga en cuenta que usé el hecho de la simetría con respecto a x = 0. Si las elipses se desplazan con respecto a x, el código debe modificarse un poco. También tenga en cuenta que el orden en que se proporcionan las elipsis es importante!