Numpy: Creación "controlada" de matrices de objetos

Creado en 4 dic. 2019  ·  45Comentarios  ·  Fuente: numpy/numpy

La creación automática de matrices de objetos quedó obsoleta recientemente en numpy. Estoy de acuerdo con el cambio, pero parece un poco difícil escribir ciertos tipos de código genérico que determinan si un argumento proporcionado por el usuario es convertible en una matriz sin objeto.

Ejemplo de código de reproducción:

Matplotlib contiene el siguiente fragmento:

    # <named ("string") colors are handled earlier>
    # tuple color.
    c = np.array(c)
    if not np.can_cast(c.dtype, float, "same_kind") or c.ndim != 1:
        # Test the dtype explicitly as `map(float, ...)`, `np.array(...,
        # float)` and `np.array(...).astype(float)` all convert "0.5" to 0.5.
        # Test dimensionality to reject single floats.
        raise ValueError(f"Invalid RGBA argument: {orig_c!r}")

pero a veces se llama a la función con una matriz de colores en varios formatos (por ejemplo, ["red", (0.5, 0.5, 0.5), "blue"] ); capturamos el ValueError y convertimos cada elemento de uno en uno.

Ahora la llamada a np.array (c) emitirá un DeprecationWarning. ¿Cómo podemos solucionar eso? Incluso algo como np.min_scalar_type(c) emite una advertencia (¿que supongo que no debería?), Por lo que no es obvio para mí cómo verificar "si convirtiéramos esto en una matriz, ¿cuál sería el tipo d?"

Información de la versión de Numpy / Python:


1.19.0.dev0 + bd1adc3 3.8.0 (predeterminado, 6 de noviembre de 2019, 21:49:08)
[GCC 7.3.0]

57 - Close?

Comentario más útil

¿Alguien podría señalar el ejemplo operator.mod ?

En cuanto al operador == , el que vi estaba haciendo algo como np.array(vals, dtype=object) == vals donde vals=[1, [2, 3]] (parafraseando el código), por lo que la solución es crear proactivamente la matriz de la derecha lado.

Muchas de las fallas de scipy parecen ser de la forma np.array([0.25, np.array([0.3])]) , donde mezclar escalares y ndarray con shape==(1,) entrará en conflicto con el descubrimiento de dimensiones y creará una matriz de objetos. xref gh-15075

Todos 45 comentarios

Una opcion seria
`` pitón
tratar:
# adelantarse al juego y promover la desaprobación a un error que lo reemplazará
con warnings.catch_warnings ():
warnings.filterwarnings ('subir', DeprecationWarning, message = "...")
c_arr = np.asarray (c)
excepto (DeprecationWarning, ValueError):
# lo que sea que haga actualmente para ValueError

Supongo que esto, y la prueba fallida mencionada en gh-15045 son casos en los que emitir un DeprecationWarning durante unos años en lugar de emitir directamente un ValueError provoca más abandono de código de lo necesario.

Tenga en cuenta que warnings.catch_warnings no es seguro para subprocesos. Eso hace que la solución sea un poco propensa a problemas de seguimiento en el futuro.

Creo que el abandono de códigos vale la pena el período de desaprobación.

Matplotlib ejecuta su conjunto de pruebas con advertencias como fallas para detectar exactamente este tipo de cambio temprano, por lo que me parece que el sistema funciona :).

Pero AFAICT, ni siquiera hay una solución razonablemente fácil (como se señaló anteriormente, la solución propuesta no es segura para subprocesos): /

Creo que veo el punto de @anntzer aquí. Estamos en un lío en el que la biblioteca descendente quiere fallar rápidamente para poder probar otra cosa, mientras que a los usuarios se les debe mostrar un mensaje más suave.

El problema es que hoy en día no hay forma de que el autor de la biblioteca pregunte "¿esto emitiría una advertencia?" Sin en realidad ... emitir la advertencia, y suprimirla no es seguro para los subprocesos.

Con respecto a la seguridad de los subprocesos de advertencia: https://bugs.python.org/issue37604

AFAIK, la desaprobación está en la rama de lanzamiento. ¿Queremos revertirlo? De lo contrario, las correcciones necesitarán backports. Todavía no tengo claro por qué las advertencias no se generaron en las ruedas de la rama de lanzamiento y no aparecieron en las compilaciones nocturnas hasta las dos últimas compilaciones. No cambié nada después de la rama y nada parece muy sospechoso en las confirmaciones desde entonces en la rama maestra excepto, posiblemente, # 15040.

En mi humilde opinión (y de acuerdo con el punto anterior de @mattip ), es el tipo de cambios que serían mucho más fáciles de manejar en sentido descendente si el cambio a la recaudación ocurriera sin un período de depreciación. Sin embargo, no estoy seguro de que sea una opción: /

O posiblemente las ramas de los premios multibuild sean diferentes a las maestras.

FWIW Siempre tuve al menos -1 en este cambio, especialmente como un usuario entusiasta de estructuras de datos irregulares, pero de todos modos ahora necesito averiguar qué hacer con los cientos de fallas de prueba para la preparación de SciPy 1.4.0rc2 en https://github.com/scipy/scipy/pull/11161

ahora necesito averiguar qué hacer con los cientos de fallos de prueba

Una opción fácil sería:

  • Suprime la advertencia en tu configuración de Pytest
  • Abra un problema para solucionarlo más tarde

El objetivo de que usáramos DeprecationWarning lugar de ValueError era dar a los proyectos y usuarios posteriores un período de gracia para hacer exactamente eso.

AFAIK, la desaprobación está en la rama de lanzamiento. ¿Queremos revertirlo?

Creo que sí, está lloviendo. Ahora tenemos una lista de lo que se está rompiendo en Pandas, Matplotlib, SciPy, dentro de numpy.testing y NumPy ufuncs, == , etc. Creo que deberíamos revertir el cambio ahora e ir a evaluar / corregir todos esos cosas, luego reintroduzca la desaprobación.

¿Podemos comprometernos con una advertencia pendiente de eliminación?

De esa manera, los proyectos posteriores pueden agregarlo a sus listas de ignorados, y cuando volvemos a DeprecationWarning, ellos pueden tomar la decisión nuevamente.

Parece que nos hemos apartado del problema original, que parece ser "dada una secuencia de valores, ¿cómo puede matplotlib determinar si son un solo color o una lista de colores?". Creo que debería haber una solución que no requiera convertir los valores en un ndarray y verificar el tipo de ese arreglo. Algún tipo de función is_a_color() recursiva podría ser una mejor solución.

Revertí el cambio de 1.18.x en # 15053.

El sentimiento es que romper CI de scipy y pandas es lo suficientemente molesto como para revertirlo temporalmente en master también. Sin embargo, me gustaría que volviera a estar básicamente programado (digamos dentro de un mes). Sin embargo, es posible que necesitemos encontrar una solución. Además, las reparaciones que están haciendo los pandas son un poco preocupantes para mí, ya que usan catch_warnings .

Si realmente no hay forma, y ​​necesitamos supresión de advertencias seguras para subprocesos. np.seterr posiblemente podría tener un espacio para ello: /.

Parece que nos hemos apartado del problema original, que parece ser "dada una secuencia de valores, ¿cómo puede matplotlib determinar si son un solo color o una lista de colores?".

Sin embargo, creo que el problema que

  • crear ndarray(flexible_input)
  • if `new_ndarray.dtype.kind == 'O': maneja esto
  • más: use_the_array

ya que no se puede agregar dtype=object a dicho código, ¿qué se debe hacer?

Además, las reparaciones que están haciendo los pandas son un poco preocupantes para mí, ya que usan catch_warnings .

@seberg no fue suppress_warnings mejor para esto?

@rgommers no, suppress_warnings resolvió el problema de que la supresión de advertencias fuera permanente cuando no debería serlo. Sin embargo, eso se ha solucionado en las versiones más nuevas de Python, por lo que realmente ya no lo necesitamos (tiene mejores propiedades, ya que admite el anidamiento, pero no admite la seguridad de subprocesos. No estoy seguro de que sea posible fuera de Python, e incluso si lo fue, probablemente no sea deseable)

No estoy completamente seguro de si los casos problemáticos se contraponen a la intención original (https://numpy.org/neps/nep-0034.html) de ellos, simplemente no nos anticipamos.

De todos modos, una salida sería habilitar explícitamente el antiguo comportamiento en la línea de "apreciar su preocupación, pero queremos explícitamente el tipo de objeto dependiente del contexto y manejaremos la entrada problemática nosotros mismos". Algo como uno de

~~~
np.array (datos, dtype = 'allow_object')

np.array (datos, allow_object_dtype = True)

con np.array_create_allow_object_dtype ():
np.array (datos)
~~~

todo no es muy bonito y el nombre seguramente será mejorado. Pero esto ofrece una salida limpia para las bibliotecas que se basaron en el comportamiento y quieren mantenerlo (al menos por el momento).

¿No es el caso matplotlib en realidad:

with np.forbid_ragged_arrays_immediately():
    np.array(data)

ya que realmente desea detectar el error, en lugar de obtener un objeto dtype?

No hay reversión de la baja pendiente actualmente para la versión maestra. No creo que deba revertirse al por mayor como estaba en 1.18 porque eso también eliminó las correcciones, que creo que queremos mantener. @mattip Se

FWIW Creo que la mayoría de los lugares en mpl que golpean esto se pueden arreglar (con más o menos reestructuración; en un caso, el código resulta mucho más rápido después de ...).
Creo que la API propuesta por @timhoffm sería mejor que una with np.forbid_ragged_arrays_immediately: porque la última se puede escribir fácilmente en términos de la primera (aumentar si np.array(..., allow_object=True).dtype == object ) mientras que lo contrario ( try: with np.forbid: ... except ValueError: ... ) sería menos eficiente si aún queremos crear una matriz de objetos después de todo. Pero un CM (simplemente "moverse localmente más allá del período de depreciación") sería mejor que nada.

(Nuevamente, creo que el cambio es bueno, es solo una cuestión de cómo se ejecuta).

Sí, solo tenemos que averiguar cómo debería verse la API. Como han señalado muchos, actualmente hay dos problemas principales:

  1. Confundir object y "allow ragged" . Si los objetos tienen un tipo razonable (digamos Decimal ), en realidad desea obtener la advertencia / error, pero también es posible que deba pasar dtype=object
  2. No hay forma de participar en el nuevo comportamiento o seguir usando el antiguo (sin una advertencia). Parece que al menos Opt-In es probablemente necesario para el uso interno, si no lo proporcionamos, básicamente asumimos que (posiblemente indirectamente) solo los usuarios finales se encuentran con estos casos.

Finalmente, tenemos que descubrir cómo meterlo en nuestro código :). ndmin puede ser otro objetivo para meter en fllags que controlen el comportamiento irregular al menos.

No hay reversión de la baja pendiente actualmente para la versión maestra. No creo que deba revertirse al por mayor como estaba en 1.18 porque eso también eliminó las correcciones, que creo que queremos mantener. @mattip Se

No veo ningún problema con una reversión completa y luego reintroducir las partes que tengan sentido ahora. Nuevamente, revertir algo no es un juicio de valor sobre lo que es bueno o malo, es solo una forma pragmática de deshacer un montón de cosas que acabamos de romper presionando el botón de fusión. Es evidente que hay problemas de impacto y sin resolver que no estaban previstos en la NEP, por lo que revertir primero es lo correcto.

Un argumento para no revertir todavía: mientras el cambio está en el maestro, podemos aprovechar las ejecuciones de CI posteriores para tratar de averiguar cómo se verían sus soluciones alternativas

El CI aguas abajo es rojo, eso es _muy_ inútil. Ahora tenemos su lista de fallas, no necesitamos mantener sus CI en rojo para hacer nuestra vida un poco más fácil aquí.

Y al menos el CI de Matplotlib se ejecuta contra pip install --pre no rama maestra

Y al menos el CI de Matplotlib se ejecuta contra pip install --pre no rama maestra

Parece que está tirando de las ruedas nocturnas. El cambio ya se revirtió para 1.18.0rc1, por lo que no debería verlo si estuviera instalando con --pre de PyPI.

Algunos de los comentarios anteriores equivalen a repensar los cambios propuestos en NEP 34. No estoy seguro de si este hilo es el lugar apropiado para continuar esta discusión, pero aquí va. (No hay problema si se debe discutir en otro lugar; copiar y pegar comentarios es fácil.: Smile: Además, algunos de ustedes han visto una variación en estos comentarios en una discusión sobre holgura).

Después de pensar en esto recientemente, terminé con la misma idea que la primera sugerencia de @timhoffm (y la idea probablemente se ha propuesto en otras ocasiones en los últimos meses): definir una cadena específica o un objeto singleton que, cuando se da como el argumento dtype de array , permite que la función maneje la entrada de forma irregular creando una matriz de objetos 1-d. En efecto, esto habilita el comportamiento anterior a NEP-34 de dtype=None en el que la entrada con forma irregular se convierte automáticamente en una matriz de objetos. Si se da cualquier otro valor para dtype (incluido None o object ), se da una advertencia de depreciación si la entrada tiene forma irregular. En una versión futura de NumPy, esa advertencia se convertirá en un error.

Creo que ahora está claro que usar dtype=object para permitir el manejo de entradas irregulares no es una buena solución al problema. Idealmente, desacoplaríamos las nociones de "matriz de objetos" de "matriz irregular". Pero no podemos desacoplarlos por completo, porque cuando queremos manejar una matriz irregular, la única opción que tenemos es crear una matriz de objetos. Por otro lado, a veces queremos una matriz de objetos, pero no queremos la conversión automática de una entrada con forma irregular en una matriz de objetos de secuencias.

Por ejemplo (cf. elemento 1 en el último comentario de @seberg ), suponga que f1 , f2 , f3 y f4 son Fraction objetos, y estoy trabajando con matrices de objetos de Fraction s. No estoy interesado en crear una matriz irregular. Si escribo accidentalmente a = np.array([f1, f2, [f3, f4]], dtype=object) , _quiero_ que eso genere un error, por todas las razones por las que tenemos NEP 34. Con NEP 34, sin embargo, eso creará una matriz 1-d de longitud 3.

Las alternativas que agregan un nuevo argumento de palabra clave, como la segunda sugerencia de @timhoffm , parecen más complicadas de lo necesario. El problema que estamos tratando de resolver es el "pistoletazo de salida", donde la entrada irregular se convierte automáticamente en una matriz de objetos 1-d. El problema solo surge cuando dtype=None se pasa a array . Exigir a los usuarios que reemplacen dtype=None con dtype=<special-value-that-enables-ragged-handling> para mantener el antiguo comportamiento problemático es un cambio simple en la API que es fácil de explicar. ¿Realmente necesitamos algo más que eso?

Creo que ahora está claro que usar dtype=object para habilitar el manejo de entradas irregulares no es una buena solución al problema. Idealmente, desacoplaríamos las nociones de "matriz de objetos" de "matriz irregular".

Suena razonable, tal vez. También es bueno señalar que no existe un concepto real de "matriz irregular" en NumPy . Es algo que básicamente no admitimos (busque "irregular" en los documentos, en el rastreador de problemas o en la lista de correo para confirmar si lo desea), es algo que DyND y XND admiten, y solo comenzamos a hablar para tener una concisa frase para discutir "queremos eliminar el comportamiento np.array([1, [2, 3]]) que hace tropezar a los usuarios". Por lo tanto, hornear en "matrices irregulares" como una nueva API debe hacerse con extrema precaución, no es algo que queremos promover. Por lo tanto, sería bueno dejarlo en claro al nombrar cualquier dtype=some_workaround que podamos agregar.

Parece que la opinión general se está fusionando en torno a una solución para extender la depreciación (tal vez indefinidamente) al permitir np.array(vals, dtype=special) que se comportará como antes de NEP 34. Prefiero un singleton en lugar de una cadena, ya que significa que los usos de la biblioteca pueden hacer special = getattr(np.special, None) y su código funcionará en todas las versiones.

Ahora tenemos que decidir el nombre y dónde debe exponerse. ¿Quizás never_fail o guess_dimensions ? En cuanto a dónde exponerlo, preferiría no colgarlo np lugar de otro módulo interno, tal vez con un _ para indicar que realmente es una interfaz privada.

Creo que el camino a seguir es enmendar NEP 34 y luego exponer la discusión en la lista de correo.

Tenga en cuenta que también ha habido un par de informes de problemas con el uso de operadores ( == y operator.mod al menos). ¿Está proponiendo ignorar eso o almacenar de alguna manera ese estado en la matriz?

En casi todos los casos, probablemente se sepa que uno de los operandos es una matriz numpy. Por lo tanto, probablemente debería ser posible obtener un comportamiento bien definido convirtiéndolo manualmente en una matriz numpy.

¿Alguien podría señalar el ejemplo operator.mod ?

En cuanto al operador == , el que vi estaba haciendo algo como np.array(vals, dtype=object) == vals donde vals=[1, [2, 3]] (parafraseando el código), por lo que la solución es crear proactivamente la matriz de la derecha lado.

Muchas de las fallas de scipy parecen ser de la forma np.array([0.25, np.array([0.3])]) , donde mezclar escalares y ndarray con shape==(1,) entrará en conflicto con el descubrimiento de dimensiones y creará una matriz de objetos. xref gh-15075

¿Alguien podría señalar el ejemplo operator.mod ?

Lo vi en Pandas PR de @jbrockmendel , pero creo que ha cambiado desde entonces (ya no veo un operator.mod explícito en los comentarios).

En cuanto al operador == , el que vi estaba haciendo algo como np.array(vals, dtype=object) == vals donde vals=[1, [2, 3]] (parafraseando el código), por lo que la solución es crear proactivamente la matriz de la derecha lado.

En ese punto se convierte en np.array(vals, dtype=object) == np.array(vals, dtype=object) , así que mejor simplemente borre la prueba :)

@mattip escribió:

Prefiero un singleton en lugar de una cadena, ya que significa que los usos de la biblioteca pueden hacer special = getattr (np.special, None) y su código funcionará en todas las versiones.

Eso me suena bien.

Ahora tenemos que decidir el nombre y dónde debe exponerse. ¿Quizás never_fail o guess_dimensions ? En cuanto a dónde exponerlo, preferiría no colgarlo np en lugar de otro módulo interno, tal vez con un _ para indicar que realmente es una interfaz privada.

Mi nombre de trabajo actual para esto es legacy_auto_dtype , pero probablemente hay muchos otros nombres sobre los que no tendría quejas.

No estoy seguro de que el nombre deba ser privado. Según cualquier definición práctica de _private_ y _public_, este será un objeto _public_. Proporciona a los usuarios los medios para preservar el comportamiento heredado de, por ejemplo, array(data) reescribiéndolo como array(data, dtype=legacy_auto_dtype) . Me imagino que el NEP actualizado explicará que así es como se debe modificar el código para mantener el comportamiento heredado (para aquellos que deben hacerlo). Si ese es el caso, el objeto definitivamente no es privado. De hecho, parece que es un objeto público que permanecerá en NumPy de forma indefinida. Pero quizás mi comprensión de cómo se desarrollará el NEP 34 modificado es incorrecta.

De acuerdo con la descripción de @WarrenWeckesser de público / privado; o es público o no debería ser utilizado por nadie fuera de NumPy.

Re name: elija un nombre que describa la funcionalidad. Cosas como "legado" casi nunca son una buena idea.

elija un nombre que describa la funcionalidad.

auto_object , auto_dtype , auto ?

Pensando en voz alta por un momento ...

¿Qué hace este objeto?

Actualmente, cuando NumPy recibe un objeto Python que contiene subsecuencias cuyas longitudes no son consistentes con una matriz nd regular, NumPy creará una matriz con el tipo de datos object , con los objetos en el primer nivel donde ocurre la inconsistencia de la forma. a la izquierda como objetos de Python. Por ejemplo, array([[1, 2], [1, 2, 3]]) tiene forma (2,) , np.array([[1, 2], [3, [99]]]) tiene forma (2, 2) , etc. Con NEP 34, estamos desaprobando ese comportamiento, por lo que intentamos crear un La matriz con entrada "irregular" eventualmente dará como resultado un error, a menos que esté explícitamente habilitada. El valor especial del que estamos hablando habilita el comportamiento anterior.

¿Cuál es un buen nombre para eso ? ragged_as_object ? inconsistent_shapes_as_object ?

En ese punto, se convierte en np.array(vals, dtype=object) == np.array(vals, dtype=object) , así que es mejor eliminar la prueba :)

Bueno, estaba parafraseando. La prueba real es más como my_func(vals) == vals debería convertirse en my_func(vals) == np.array(vals, dtype=object)

Propondré una extensión de NEP 34 para permitir un valor especial para dtype.

Tenga en cuenta que parece que scipy no necesita este centinela para pasar las pruebas con scipy / scipy # 11310 y scipy / scipy # 11308

gh-15119 se fusionó, lo que volvió a implementar la NEP. Si no se revierte, podemos cerrar este problema.

Voy a cerrar esto, ya que no hicimos un seguimiento antes de la versión 1.19. Y al menos espero que la razón de esto sea porque la discusión se ha calmado ya que todos los proyectos importantes pudieron encontrar soluciones razonables a los problemas creados por ella.
Por favor, corríjame si me equivoco, especialmente si esto todavía es propenso a problemas con pandas, matplotlib, etc. Pero supongo que nos habríamos enterado de eso durante el ciclo de candidatos de lanzamiento 1.19.x.

¿Fue útil esta página
0 / 5 - 0 calificaciones