web-dev-qa-db-fra.com

Conversion d'un tableau structuré en tableau numpy à utiliser avec Scikit-Learn

J'ai des difficultés à convertir un tableau structuré chargé à partir d'un fichier CSV à l'aide de np.genfromtxt en un np.array afin d'adapter les données à un estimateur Scikit-Learn. Le problème est qu’à un moment donné, une conversion du tableau structuré en un tableau normal se produira, donnant lieu à un ValueError: can't cast from structure to non-structure. Cela faisait longtemps que j'utilisais .view pour effectuer la conversion, mais cela a entraîné un certain nombre d'avertissements relatifs à la dépréciation de NumPy. Le code est comme suit:

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)

L'exception levée est: ValueError: Can't cast from structure to non-structure, except if the structure only has a single field.

Ma deuxième tentative a été d'utiliser une vue comme suit:

# View is raising deprecation warnings
X = data[features]
X = X.view((float, len(X.dtype.names)))
y = data[target].astype(int)

Ce qui fonctionne et fait exactement ce que je veux qu'il fasse (je n'ai pas besoin d'une copie des données), mais entraîne des avertissements de dépréciation:

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.

Pour le moment, nous utilisons tolist() pour convertir le tableau structuré en liste, puis en np.array. Cela fonctionne, mais cela semble terriblement inefficace:

# Current method (efficient?)
X = np.array(data[features].tolist())
y = data[target].astype(int)

Il doit y avoir un meilleur moyen, je vous serais reconnaissant de tout conseil. 

NOTE: les données de cet exemple proviennent du Répertoire d'occupation de UCI ML et les données s'affichent comme suit: 

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')])
8
bbengfort

Ajoutez une .copy() à data[features]

X = data[features].copy()
X = X.view((float, len(X.dtype.names)))

et le message FutureWarning est parti.

Cela devrait être plus efficace que de convertir en une liste en premier.

3
Mike Müller

Vous pouvez éviter la copie si vous pouvez d'abord lire les données dans un tableau NumPy ordinaire (en omettant le paramètre names):

data = np.genfromtxt(path, dtype=float, delimiter=',', skip_header=1)

Ensuite (heureusement pour nous), X est composé de toutes les colonnes sauf la première et la dernière (c.-à-d. En omettant les colonnes datetime et occupancy). Nous pouvons donc exprimer X et y sous forme de tranches:

X = data[:, 1:-1]
y = data[:, -1].astype(int)

Ensuite, nous pouvons passer facilement ces fonctions aux fonctions d’apprentissage de scikit:

clf = GradientBoostingClassifier(random_state=42)
clf.fit(X, y)

et, si nous le souhaitons, nous pouvons voir le tableau NumPy comme un tableau structuré par la suite:

features = ["temperature", "relative_humidity", "light", "C02", "humidity"]
X = X.ravel().view([(field, X.dtype.type) for field in features])

Malheureusement, cette solution de contournement repose sur le fait que X peut être exprimé sous forme de tranche - nous ne pourrions pas éviter de copier si occupancy apparaissait entre les autres colonnes de fonctions, par exemple. Cela signifie également que vous devez définir X en utilisant X = data[:, 1:-1] au lieu du X = data[features], plus compréhensible pour l'homme,. 


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 vous devez commencer par le tableau structuré, alors la réponse de hpaulj montre comment view/reshape/slice le tableau structuré pour obtenir un tableau en clair sans copier: 

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)

Cela tire parti du fait que chaque champ a une longueur de 8 octets. Il est donc facile de convertir le tableau structuré en un tableau simple de type <f8. Le remodelage en fait un tableau 2D avec le même nombre de lignes. Le découpage en tranches supprime les colonnes/champs datetime et occupancy du tableau.

1
unutbu