2015-01-20 19 views
5

Ho una funzione in cui normalizzo le prime N colonne di un DataFrame. Voglio restituire il DataFrame normalizzato, ma lasciare l'originale da solo. Tuttavia, sembra che la funzione muti anche il DataFrame passato!Julia: il passaggio di un DataFrame a una funzione crea un puntatore a DataFrame?

using DataFrames 

function normalize(input_df::DataFrame, cols::Array{Int}) 
    norm_df = input_df 
    for i in cols 
     norm_df[i] = (input_df[i] - minimum(input_df[i]))/
      (maximum(input_df[i]) - minimum(input_df[i])) 
    end 
    norm_df 
end 

using RDatasets 
iris = dataset("datasets", "iris") 
println("original df:\n", head(iris)) 

norm_df = normalize(iris, [1:4]); 
println("should be the same:\n", head(iris)) 

uscita:

original df: 
6x5 DataFrame 
| Row | SepalLength | SepalWidth | PetalLength | PetalWidth | Species | 
|-----|-------------|------------|-------------|------------|----------| 
| 1 | 5.1   | 3.5  | 1.4   | 0.2  | "setosa" | 
| 2 | 4.9   | 3.0  | 1.4   | 0.2  | "setosa" | 
| 3 | 4.7   | 3.2  | 1.3   | 0.2  | "setosa" | 
| 4 | 4.6   | 3.1  | 1.5   | 0.2  | "setosa" | 
| 5 | 5.0   | 3.6  | 1.4   | 0.2  | "setosa" | 
| 6 | 5.4   | 3.9  | 1.7   | 0.4  | "setosa" | 

should be the same: 
6x5 DataFrame 
| Row | SepalLength | SepalWidth | PetalLength | PetalWidth | Species | 
|-----|-------------|------------|-------------|------------|----------| 
| 1 | 0.222222 | 0.625  | 0.0677966 | 0.0416667 | "setosa" | 
| 2 | 0.166667 | 0.416667 | 0.0677966 | 0.0416667 | "setosa" | 
| 3 | 0.111111 | 0.5  | 0.0508475 | 0.0416667 | "setosa" | 
| 4 | 0.0833333 | 0.458333 | 0.0847458 | 0.0416667 | "setosa" | 
| 5 | 0.194444 | 0.666667 | 0.0677966 | 0.0416667 | "setosa" | 
| 6 | 0.305556 | 0.791667 | 0.118644 | 0.125  | "setosa" | 

risposta

9

Julia usa un comportamento noto come "pass-by-sharing". Dai documenti (sottolineatura mia):

Gli argomenti della funzione Julia seguono una convenzione chiamata talvolta "condivisione per condivisione", che significa che i valori non vengono copiati quando vengono passati alle funzioni. Gli stessi argomenti delle funzioni fungono da nuove associazioni di variabili (nuove posizioni che possono riferirsi ai valori), ma i valori a cui si riferiscono sono identici ai valori passati. Le modifiche ai valori mutabili (come matrici) effettuate all'interno di una funzione saranno visibili al chiamante. Questo è lo stesso comportamento trovato in Scheme, la maggior parte dei Lisps, Python, Ruby e Perl, tra gli altri linguaggi dinamici.

Nel tuo caso particolare, ciò che sembra voler creare è un DataFrame completamente nuovo e indipendente per la tua operazione di normalizzazione. Puoi farlo utilizzando deepcopy, ad es.

norm_df = deepcopy(input_df) 

Julia tipicamente richiederà di fare questo genere di cose in modo esplicito dal momento che la creazione di una copia indipendente di un grande data-telaio può essere computazionalmente costoso e Julia è un linguaggio orientato alle prestazioni.

Sempre dalla documentazione, notare il seguente importante differenza tra copy e deepcopy:

copy(x): Creare una copia di x: la struttura esterna viene copiato, ma non tutti i valori interni. Ad esempio, la copia di un array produce un nuovo array con gli stessi identici elementi dell'originale.

deepcopy(x): crea una copia profonda di x: tutto viene copiato ricorsivamente, dando origine a un oggetto completamente indipendente. Ad esempio, la copia profonda di un array produce un nuovo array i cui elementi sono copie in profondità degli elementi originali.

tipo DataFrame è array simile, e così deepcopy è necessario.

Una domanda SO correlata è here.

+0

Grazie! Quindi in quali casi è un oggetto copiato o copiato in profondità? Ad esempio, se lo faccio, 'iris2 = iris' e quindi passiamo' iris2' a 'normalize()', anche 'iris' viene modificato? –

+0

@AlexanderFlyax Sì, 'iris' verrebbe modificato. In genere qualsiasi operazione della forma 'y = x' non * creerà * un nuovo oggetto indipendente, indipendentemente dal tipo (sebbene sia interessante, per i vettori,' y = x [1: end] '* will * (penso) crei un oggetto indipendente). Per tipi concreti come 'Int64',' copy' * creerà un nuovo oggetto indipendente, ma per 'Array {Int64}', 'copy' crea solo una struttura esterna indipendente, ma non elementi interni indipendenti, quindi la necessità di' deepcopy' quando si tratta di matrici. 'DataFrame' è array-like, e quindi' deepcopy' è necessario per questo. –

+0

Grazie per la tua spiegazione.Non sto cercando di essere critico nei confronti di Julia, solo cercando di capire: perché dovrei fare una copia di un array se la modifica della copia modifica l'array originale? Se volessi modificare l'array originale, lo farei, no? Ad esempio, sembra al meglio ridondante e, nel peggiore dei casi, controproducente. Mi sto perdendo qualcosa? –