2017-10-20 38 views
4

Dans un travail explicatif rapide, IndexedTables semble beaucoup plus rapide que DataFrames pour travailler sur des éléments individuels (par exemple sélectionner ou "mise à jour"), mais DataFrames ont un meilleur écosystème de fonctionnalités, par ex. tracer, exporter ..Comment convertir un IndexedTable en un DataFrame dans Julia?

Ainsi, à un certain point du flux de travail, je voudrais convertir l'IndexedTable à un DataFrame, par exemple.

using DataFrames, IndexedTables, IndexedTables.Table 

tn = Table(
    Columns(
     param = String["price","price","price","price","waterContent","waterContent"], 
     item = String["banana","banana","apple","apple","banana", "apple"], 
     region = Union{String,DataArrays.NAtype}["FR","UK","FR","UK",NA,NA] 
    ), 
    Columns(
     value2000 = Float64[2.8,2.7,1.1,0.8,0.2,0.7], 
     value2010 = Float64[3.2,2.9,1.2,0.8,0.2,0.8], 
    ) 
) 

à >>

df_tn = DataFrame(
    param  = String["price","price","price","price","waterContent","waterContent"], 
    item  = String["banana","banana","apple","apple","banana", "apple"], 
    region = Union{String,DataArrays.NAtype}["FR","UK","FR","UK",NA,NA], 
    value2000 = Float64[2.8,2.7,1.1,0.8,0.2,0.7], 
    value2010 = Float64[3.2,2.9,1.2,0.8,0.2,0.8], 
) 

ou

t = Table(
    Columns(
     String["price","price","price","price","waterContent","waterContent"], 
     String["banana","banana","apple","apple","banana", "apple"], 
     Union{String,DataArrays.NAtype}["FR","UK","FR","UK",NA,NA] 
    ), 
    Columns(
     Float64[2.8,2.7,1.1,0.8,0.2,0.7], 
     Float64[3.2,2.9,1.2,0.8,0.2,0.8], 
    ) 
) 

à >>

df_t = DataFrame(
    x1 = String["price","price","price","price","waterContent","waterContent"], 
    x2 = String["banana","banana","apple","apple","banana", "apple"], 
    x3 = Union{String,DataArrays.NAtype}["FR","UK","FR","UK",NA,NA], 
    x4 = Float64[2.8,2.7,1.1,0.8,0.2,0.7], 
    x5 = Float64[3.2,2.9,1.2,0.8,0.2,0.8] 
) 

je peux trouver l'individu "ligne" des valeurs d'interaction sur la table avec pair() :Je ne peux cependant pas obtenir les noms et les types des colonnes, et je suppose que travailler par colonne serait plutôt plus efficace.

EDIT: J'ai réussi à obtenir les types "colonne" avec le code ci-dessus, je dois tout à l'heure pour obtenir les noms de colonnes, le cas échéant:

colTypes = Union{Union,DataType}[] 

for item in tn.index.columns 
    push!(colTypes, eltype(item)) 
end 
for item in tn.data.columns 
    push!(colTypes, eltype(item)) 
end 

EDIT2: Comme l'a demandé, ceci est un exemple d'IndexedTable qui échouerait la conversion des noms de colonnes en utilisant la réponse (actuelle) Dan Getz, car les colonnes "index" sont nommées tuple mais les colonnes "data" sont des tuples normaux:

t_named_idx = Table(
    Columns(
     param = String["price","price","price","price","waterContent","waterContent"], 
     item = String["banana","banana","apple","apple","banana", "apple"], 
     region = Union{String,DataArrays.NAtype}["FR","UK","FR","UK",NA,NA] 
    ), 
    Columns(
     Float64[2.8,2.7,1.1,0.8,0.2,0.7], 
    ) 
) 

Le problème semble être dans l'API IndexedTable, et en particulier dans la fonction columns(t), qui ne fait pas la distinction entre l'index et les valeurs.

Répondre

3

Les fonctions de conversion suivantes:

toDataFrame(cols::Tuple, prefix="x") = 
    DataFrame(;(Symbol("$prefix$c") => cols[c] for c in fieldnames(cols))...) 

toDataFrame(cols::NamedTuples.NamedTuple, prefix="x") = 
    DataFrame(;(c => cols[c] for c in fieldnames(cols))...) 

toDataFrame(t::IndexedTable) = toDataFrame(columns(t)) 

donner (sur Julia 0.6 avec tn et t définis comme dans la question):

julia> tn 
param   item  region │ value2000 value2010 
─────────────────────────────────┼───────────────────── 
"price"   "apple" "FR" │ 1.1  1.2 
"price"   "apple" "UK" │ 0.8  0.8 
"price"   "banana" "FR" │ 2.8  3.2 
"price"   "banana" "UK" │ 2.7  2.9 
"waterContent" "apple" NA  │ 0.7  0.8 
"waterContent" "banana" NA  │ 0.2  0.2 

julia> df_tn = toDataFrame(tn) 
6×5 DataFrames.DataFrame 
│ Row │ param   │ item  │ region │ value2000 │ value2010 │ 
├─────┼────────────────┼──────────┼────────┼───────────┼───────────┤ 
│ 1 │ "price"  │ "apple" │ "FR" │ 1.1  │ 1.2  │ 
│ 2 │ "price"  │ "apple" │ "UK" │ 0.8  │ 0.8  │ 
│ 3 │ "price"  │ "banana" │ "FR" │ 2.8  │ 3.2  │ 
│ 4 │ "price"  │ "banana" │ "UK" │ 2.7  │ 2.9  │ 
│ 5 │ "waterContent" │ "apple" │ NA  │ 0.7  │ 0.8  │ 
│ 6 │ "waterContent" │ "banana" │ NA  │ 0.2  │ 0.2  │ 

Les informations de type est principalement retenu:

julia> typeof(df_tn[:,1]) 
DataArrays.DataArray{String,1} 

julia> typeof(df_tn[:,4]) 
DataArrays.DataArray{Float64,1} 

Et pour les colonnes sans nom:

julia> t 
───────────────────────────────┬───────── 
"price"   "apple" "FR" │ 1.1 1.2 
"price"   "apple" "UK" │ 0.8 0.8 
"price"   "banana" "FR" │ 2.8 3.2 
"price"   "banana" "UK" │ 2.7 2.9 
"waterContent" "apple" NA │ 0.7 0.8 
"waterContent" "banana" NA │ 0.2 0.2 

julia> df_t = toDataFrame(t) 
6×5 DataFrames.DataFrame 
│ Row │ x1    │ x2  │ x3 │ x4 │ x5 │ 
├─────┼────────────────┼──────────┼──────┼─────┼─────┤ 
│ 1 │ "price"  │ "apple" │ "FR" │ 1.1 │ 1.2 │ 
│ 2 │ "price"  │ "apple" │ "UK" │ 0.8 │ 0.8 │ 
│ 3 │ "price"  │ "banana" │ "FR" │ 2.8 │ 3.2 │ 
│ 4 │ "price"  │ "banana" │ "UK" │ 2.7 │ 2.9 │ 
│ 5 │ "waterContent" │ "apple" │ NA │ 0.7 │ 0.8 │ 
│ 6 │ "waterContent" │ "banana" │ NA │ 0.2 │ 0.2 │ 

EDIT: Comme il est indiqué par @ Antonello le cas des tuples mixtes nommés et non nommés n'est pas géré correctement. Pour gérer correctement, nous pouvons définir:

toDataFrame(t::IndexedTable) = 
    hcat(toDataFrame(columns(keys(t)),"y"),toDataFrame(columns(values(t)))) 

Et puis, le cas mixte donne un résultat comme:

julia> toDataFrame(tn2) 
6×5 DataFrames.DataFrame 
│ Row │ param   │ item  │ region │ x1 │ x2 │ 
├─────┼────────────────┼──────────┼────────┼─────┼─────┤ 
│ 1 │ "price"  │ "apple" │ "FR" │ 1.1 │ 1.2 │ 
│ 2 │ "price"  │ "apple" │ "UK" │ 0.8 │ 0.8 │ 
│ 3 │ "price"  │ "banana" │ "FR" │ 2.8 │ 3.2 │ 
│ 4 │ "price"  │ "banana" │ "UK" │ 2.7 │ 2.9 │ 
│ 5 │ "waterContent" │ "apple" │ NA  │ 0.7 │ 0.8 │ 
│ 6 │ "waterContent" │ "banana" │ NA  │ 0.2 │ 0.2 │ 
+0

Il fonctionne plus vite que ma solution (microsecondes aussi pour les grands ensembles de données) et est évidemment beaucoup plus élégant, mais quand un seul {index, data} est un NamedTuple, toutes les colonnes sont converties en noms xi. En général, cette réponse enseigne qu'il est préférable de regarder l'api d'un module plutôt que de tenter de jouer avec ses intérieurs en jetant une objection. – Antonello

+0

J'ai remarqué le problème avec les tuples mixtes nommés et non-nommés, et en fait, une itération plus tôt de la solution a bien géré ceci. Cette solution pourrait également être modifiée un peu pour la gérer. Je vais regarder. –

+0

Ajout d'un EDIT à la réponse pour gérer le cas mixte. –

1

laid, rapide et sale « solution » (je l'espère, il est faisable d'une autre manière):

julia> df = DataFrame(
     permutedims( # <- structural transpose 
      vcat(
      reshape([j for i in keys(t) for j in i], :, length(t)) , 
      reshape([j for i in t  for j in i], :, length(t)) 
      ), 
      (2,1) 
     ) 
     ) 
6×5 DataFrames.DataFrame 
│ Row │ x1    │ x2  │ x3 │ x4 │ x5 │ 
├─────┼────────────────┼──────────┼──────┼─────┼─────┤ 
│ 1 │ "price"  │ "apple" │ "FR" │ 1.1 │ 1.2 │ 
│ 2 │ "price"  │ "apple" │ "UK" │ 0.8 │ 0.8 │ 
│ 3 │ "price"  │ "banana" │ "FR" │ 2.8 │ 3.2 │ 
│ 4 │ "price"  │ "banana" │ "UK" │ 2.7 │ 2.9 │ 
│ 5 │ "waterContent" │ "apple" │ NA │ 0.7 │ 0.8 │ 
│ 6 │ "waterContent" │ "banana" │ NA │ 0.2 │ 0.2 │ 
+0

merci .. Je 'm' "l'étudie" .. tel qu'il est, le df converti est fait de colonnes 'Any' et il ne stocke pas les noms de colonne éventuels .. – Antonello

0

Ici, il est un attampt initial d'écrire une fonction de conversion .. il conserve les noms de colonnes et le type. Ce serait bien si elle pouvait être nettoyée et implémentée dans le DataFrame ou le paquet IndexedTable comme convert(DataFrame,t::IndexedArray).

function toDataFrame(t::IndexedTable) 

    # Note: the index is always a Tuple (named or not) while the data part can be a simple Array, a tuple or a Named tuple 

    # Getting the column types.. this is independent if it is a keyed or normal IndexedArray 
    colTypes = Union{Union,DataType}[] 
    for item in t.index.columns 
     push!(colTypes, eltype(item)) 
    end 
    if(typeof(t.data) <: Vector) # The Data part is a simple Array 
     push!(colTypes, eltype(t.data)) 
    else       # The data part is a Tuple 
     for item in t.data.columns 
      push!(colTypes, eltype(item)) 
     end 
    end 
    # Getting the column names.. this change if it is a keyed or normal IndexedArray 
    colNames = Symbol[] 
    lIdx = length(t.index.columns) 
    if(eltype(t.index.columns) <: AbstractVector) # normal Tuple 
     [push!(colNames, Symbol("x",i)) for i in 1:lIdx] 
    else           # NamedTuple 
     for (k,v) in zip(keys(t.index.columns), t.index.columns) 
      push!(colNames, k) 
     end 
    end 
    if(typeof(t.data) <: Vector) # The Data part is a simple single Array 
     push!(colNames, Symbol("x",lIdx+1)) 
    else 
     lData = length(t.data.columns) 
     if(eltype(t.data.columns) <: AbstractVector) # normal Tuple 
      [push!(colNames, Symbol("x",i)) for i in (lIdx+1):(lIdx+lData)] 
     else           # NamedTuple 
      for (k,v) in zip(keys(t.data.columns), t.data.columns) 
       push!(colNames, k) 
      end 
     end 
    end 
    # building an empty DataFrame.. 
    df = DataFrame() 
    for i in 1:length(colTypes) 
     df[colNames[i]] = colTypes[i][] 
    end 
    # and finally filling the df with values.. 
    for (i,pair) in enumerate(pairs(t)) 
     rowValues = [] 
     for (j,section) in enumerate(pair) 
      for item in section 
       push!(rowValues,item) 
      end 
     end 
     push!(df, rowValues) 
    end 
    return df 
end 
+0

La solution de Dan semble être bonne. Avez-vous testé? Y at-il un problème si les données sont Vector? BTW il y a la chance que nous aurons des paquets "colle" à l'avenir. Voir: https://github.com/JuliaLang/julia/issues/2025#issuecomment-338005473 – Liso

+0

@Liso Ok, j'ai fait un essai et la solution de Dan Gets fonctionne bien (microsecondes aussi pour les grands jeux de données), sauf quand seulement l'un des {index, data} est un NamedTuple, pour lequel toutes les colonnes sont converties en noms xi. – Antonello

+0

Pouvez-vous ajouter un petit exemple où cela ne fonctionne pas avec les noms de colonnes? – Liso

0

installer uniquement IterableTables puis

using IterableTables 
df = DataFrames.DataFrame(it) 
+0

C'est bien d'entendre parler de IterableTables (et require.jl!), Mais il ne convertit que si la source est réductible à un tuple nommé. Dans mon exemple, cela fonctionne avec 'tn' mais pas (du moins, sans conversions préliminaires) avec' t'. – Antonello