Tengo dificultades para convertir una matriz estructurada cargada desde un CSV usando np.genfromtxt
en una np.array
para ajustar los datos a un estimador de Scikit-Learn. El problema es que, en algún momento, se producirá una conversión de la matriz estructurada a una matriz normal, lo que dará como resultado un ValueError: can't cast from structure to non-structure
. Durante mucho tiempo, había estado usando .view
para realizar la conversión, pero esto resultó en una serie de advertencias de desaprobación de NumPy. El código es el siguiente:
import numpy as np from sklearn.ensemble import GradientBoostingClassifier data = np.genfromtxt(path, dtype=float, delimiter=',', names=True) target = "occupancy" features = [ "temperature", "relative_humidity", "light", "C02", "humidity" ] # Doesn't work directly X = data[features] y = data[target].astype(int) clf = GradientBoostingClassifier(random_state=42) clf.fit(X, y)
La excepción que se genera es: ValueError: Can't cast from structure to non-structure, except if the structure only has a single field.
Mi segundo intento fue usar una vista de la siguiente manera:
# View is raising deprecation warnings X = data[features] X = X.view((float, len(X.dtype.names))) y = data[target].astype(int)
Que funciona y hace exactamente lo que quiero que haga (no necesito una copia de los datos), pero da como resultado advertencias de desaprobación:
FutureWarning: Numpy has detected that you may be viewing or writing to an array returned by selecting multiple fields in a structured array. This code may break in numpy 1.15 because this will return a view instead of a copy -- see release notes for details.
Por el momento, estamos usando tolist()
para convertir la matriz estructurada en una lista y luego en np.array
. Esto funciona, sin embargo, parece terriblemente ineficiente:
# Current method (efficient?) X = np.array(data[features].tolist()) y = data[target].astype(int)
Tiene que haber una mejor manera, agradecería cualquier consejo.
NOTA : Los datos para este ejemplo provienen del repositorio de ocupación de ML de UCI y los datos aparecen de la siguiente manera:
array([(nan, 23.18, 27.272 , 426. , 721.25, 0.00479299, 1.), (nan, 23.15, 27.2675, 429.5 , 714. , 0.00478344, 1.), (nan, 23.15, 27.245 , 426. , 713.5 , 0.00477946, 1.), ..., (nan, 20.89, 27.745 , 423.5 , 1521.5 , 0.00423682, 1.), (nan, 20.89, 28.0225, 418.75, 1632. , 0.00427949, 1.), (nan, 21. , 28.1 , 409. , 1864. , 0.00432073, 1.)], dtype=[('datetime', '<f8'), ('temperature', '<f8'), ('relative_humidity', '<f8'), ('light', '<f8'), ('C02', '<f8'), ('humidity', '<f8'), ('occupancy', '<f8')])
Agregue un .copy()
a data[features]
:
X = data[features].copy() X = X.view((float, len(X.dtype.names)))
y el mensaje FutureWarning
desaparece.
Esto debería ser más eficiente que convertir primero a una lista.
Podría evitar la necesidad de copiar si primero puede leer los datos en una matriz NumPy simple (al omitir el parámetro de names
):
data = np.genfromtxt(path, dtype=float, delimiter=',', skip_header=1)
Entonces (afortunadamente para nosotros), X
se compone de todas menos la primera y la última columna (es decir, omitiendo las columnas de fecha y datetime
y occupancy
). Entonces podemos expresar X
e y
como rebanadas:
X = data[:, 1:-1] y = data[:, -1].astype(int)
Luego podemos pasarlos fácilmente a las funciones de scikit-learn:
clf = GradientBoostingClassifier(random_state=42) clf.fit(X, y)
y, si lo deseamos, podemos ver la matriz NumPy simple como una matriz estructurada después:
features = ["temperature", "relative_humidity", "light", "C02", "humidity"] X = X.ravel().view([(field, X.dtype.type) for field in features])
Desafortunadamente, esta solución se basa en que X
se puede expresar como un segmento: no podríamos evitar la copia si la occupancy
apareciera entre las otras columnas de funciones, por ejemplo. También significa que tiene que definir X
usando X = data[:, 1:-1]
en lugar de X = data[features]
es más comprensible para los humanos.
import numpy as np from sklearn.ensemble import GradientBoostingClassifier data = np.genfromtxt(path, dtype=float, delimiter=',', skip_header=1) X = data[:, 1:-1] y = data[:, -1].astype(int) clf = GradientBoostingClassifier(random_state=42) clf.fit(X, y) features = ["temperature", "relative_humidity", "light", "C02", "humidity"] X = X.ravel().view([(field, X.dtype.type) for field in features])
Si debe comenzar con la matriz estructurada, la respuesta de hpaulj muestra cómo view/reshape/slice
la matriz estructurada para obtener una matriz simple sin copiar:
import numpy as np nan = np.nan data = np.array([(nan, 23.18, 27.272 , 426. , 721.25, 0.00479299, 1.), (nan, 23.15, 27.2675, 429.5 , 714. , 0.00478344, 1.), (nan, 23.15, 27.245 , 426. , 713.5 , 0.00477946, 1.), (nan, 20.89, 27.745 , 423.5 , 1521.5 , 0.00423682, 1.), (nan, 20.89, 28.0225, 418.75, 1632. , 0.00427949, 1.), (nan, 21. , 28.1 , 409. , 1864. , 0.00432073, 1.)], dtype=[('datetime', '<f8'), ('temperature', '<f8'), ('relative_humidity', '<f8'), ('light', '<f8'), ('C02', '<f8'), ('humidity', '<f8'), ('occupancy', '<f8')]) target = 'occupancy' nrows = len(data) X = data.view('<f8').reshape(nrows, -1)[:, 1:-1] y = data[target].astype(int)
Esto aprovecha el hecho de que cada campo tiene una longitud de 8 bytes. Por lo tanto, es fácil convertir la matriz estructurada en una matriz simple de dtype <f8
. La remodelación lo convierte en una matriz 2D con el mismo número de filas. La división elimina la columna/campos de fecha y datetime
y occupancy
de la matriz.