2016-06-26 43 views
7

Ho una grande dataframe, che desidero suddiviso in una serie di test e un convoglio per la costruzione del modello. Tuttavia, non voglio duplicare DataFrame perché sto raggiungendo un limite di memoria.Divisione di un grande Pandas dataframe con il minimo ingombro

Esiste un'operazione simile al pop ma per un segmento di grandi dimensioni che rimuove contemporaneamente una parte di DataFrame e mi consente di assegnarlo a un nuovo DataFrame? Qualcosa di simile a questo:

# Assume I have initialized a DataFrame (called "all") which contains my large dataset, 
# with a boolean column called "test" which indicates whether a record should be used for 
# testing. 
print len(all) 
# 10000000 
test = all.pop_large_segment(all[test]) # not a real command, just a place holder 
print len(all) 
# 8000000 
print len(test)  
# 2000000 
+3

Per quanto ne so, nel momento in cui si esegue l'assegnazione, i panda ne creano una copia. Funzionerebbe se memorizzassi gli indici di treno e test? – ayhan

+0

Non rispondere alla domanda, ma forse altre idee pertinenti: - Non è possibile dividere il set di dati già durante il caricamento? - O usi qualcosa come 'dask' (http://dask.pydata.org/en/latest/)? –

+0

L'unico motivo per cui so è di caricarli separatamente da una tabella HDF5 e fare lo split al momento del caricamento, cioè caricare prima alcune file come allenamento e poi il resto come split può fornire una risposta appropriata è plausibile ... – RexFuzzle

risposta

3

Se avete lo spazio per aggiungere un altro della colonna, è possibile aggiungere uno con un valore casuale che si potrebbe poi filtrare avanti per la vostra prova. Qui ho usato l'uniforme tra 0 e 1, ma puoi usare qualsiasi cosa se vuoi una proporzione diversa.

df = pd.DataFrame({'one':[1,2,3,4,5,4,3,2,1], 'two':[6,7,8,9,10,9,8,7,6], 'three':[11,12,13,14,15,14,13,12,11]}) 
df['split'] = np.random.randint(0, 2, size=len(df)) 

Certo che richiede di avere lo spazio per aggiungere una colonna completamente nuovo - soprattutto se i dati sono molto lungo, forse non è così.

Un'altra opzione avrebbe funzionato, per esempio, se i dati erano in formato csv e si sapeva il numero di righe. Fare simile al precedente con la randomint, ma passare tale elenco nel skiprows argomento del Panda read_csv():

num_rows = 100000 
all = range(num_rows) 

some = np.random.choice(all, replace=False, size=num_rows/2) 
some.sort() 
trainer_df = pd.read_csv(path, skiprows=some) 

rest = [i for i in all if i not in some] 
rest.sort() 
df = pd.read_csv(path, skiprows=rest) 

E 'un po' goffo in attacco, in particolare con il ciclo nella lista di comprensione, e la creazione di tali elenchi in memoria è sfortunato, ma dovrebbe comunque essere migliore della memoria rispetto alla semplice creazione di un'intera copia di metà dei dati.

Per rendere ancora più facile la memoria, è possibile caricare il sottoinsieme del trainer, addestrare il modello, quindi sovrascrivere il dataframe dell'allenamento con il resto dei dati, quindi applicare il modello. Sarai bloccato a trasportare some e rest in giro, ma non dovrai mai caricare entrambe le metà dei dati allo stesso tempo.

1

vorrei fare qualcosa di simile a @ jeff-l, ossia mantenere la cornice di dati in archivio. Quando lo leggi come csv, usa la parola chiave chunksize. Lo script che segue illustra questo:

import pandas 
import numpy 

test = 5 
m, n = 2*test, 3 

df = pandas.DataFrame(
    data=numpy.random.random((m, n)) 
) 

df['test'] = [0] * test + [1] * test 

df.to_csv('tmp.csv', index=False) 

for chunk in pandas.read_csv('tmp.csv', chunksize=test): 
    print chunk 
    del chunk 
+0

Questo comando estrae i dati dal disco in ordine, tuttavia, e un set di dati di addestramento dovrebbe essere un campione imparziale. Anche se questo funzionerebbe se si sapesse che i dati erano già casualmente mescolati su disco .. – Jeff

1

Come altre risposte sono più concentrati sulla lettura file, immagino che anche in grado di fare qualcosa, se per qualsiasi motivo il dataframe non viene letto da un file.

Forse si può dare un'occhiata al codice della DataFrame.drop method e modificarlo al fine di modificare l'inplace dataframe (che il metodo drop già fare) e ottenere gli altri RAW restituiti:

class DF(pd.DataFrame): 
    def drop(self, labels, axis=0, level=None, inplace=False, errors='raise'): 
     axis = self._get_axis_number(axis) 
     axis_name = self._get_axis_name(axis) 
     axis, axis_ = self._get_axis(axis), axis 

     if axis.is_unique: 
      if level is not None: 
       if not isinstance(axis, pd.MultiIndex): 
        raise AssertionError('axis must be a MultiIndex') 
       new_axis = axis.drop(labels, level=level, errors=errors) 
      else: 
       new_axis = axis.drop(labels, errors=errors) 
      dropped = self.reindex(**{axis_name: new_axis}) 
      try: 
       dropped.axes[axis_].set_names(axis.names, inplace=True) 
      except AttributeError: 
       pass 
      result = dropped 

     else: 
      labels = com._index_labels_to_array(labels) 
      if level is not None: 
       if not isinstance(axis, MultiIndex): 
        raise AssertionError('axis must be a MultiIndex') 
       indexer = ~axis.get_level_values(level).isin(labels) 
      else: 
       indexer = ~axis.isin(labels) 

      slicer = [slice(None)] * self.ndim 
      slicer[self._get_axis_number(axis_name)] = indexer 

      result = self.ix[tuple(slicer)] 

     if inplace: 
      dropped = self.ix[labels] 
      self._update_inplace(result) 
      return dropped 
     else: 
      return result, self.ix[labels] 

che funzionerà in questo modo:

df = DF({'one':[1,2,3,4,5,4,3,2,1], 'two':[6,7,8,9,10,9,8,7,6], 'three':[11,12,13,14,15,14,13,12,11]}) 

dropped = df.drop(range(5), inplace=True) 
# or : 
# partA, partB = df.drop(range(5)) 

questo esempio non è probabilmente davvero efficiente della memoria, ma forse si può capire qualcosa di meglio utilizzando qualche tipo di oggetto orientato soluti su così.

+0

Immagino che questo richiederà ancora spazio extra mentre si fa quell'operazione, ma forse può essere usato in modo iterativo con piccoli blocchi. Potrebbe essere più veloce della lettura da un file. – ayhan

+0

Questo è un approccio interessante. Anche se di nuovo, sta dividendo i dati in ordine. Nei risultati 'partA' e 'partB', si finisce con la metà superiore dei dati in uno e la metà inferiore dei dati nell'altro. Campioni polarizzati a meno che i tuoi dati non vengano casualmente mescolati su disco. – Jeff

+0

@JeffL. Immagino che non sia davvero difficile raccogliere valori casuali usando un elenco di interi casuali e univoci più piccoli del numero di funzioni come argomento per la funzione di rilascio (come 'random.sample (range (len (df)), n_wanted_values) 'invece di un intervallo di valori continuo come qui con' range (5) ') – mgc