importer à partir de CSV dans un tableau Ruby, avec le 1er champ comme clé de hachage, puis rechercher la valeur d'un champ donnée ligne d'en-tête
Peut-être que quelqu'un peut m'aider.
Commençant par un fichier CSV comme ceci:
Ticker,"Price","Market Cap"
ZUMZ,30.00,933.90
XTEX,16.02,811.57
AAC,9.83,80.02
Je parviens à les lire dans un tableau:
require 'csv'
tickers = CSV.read("stocks.csv", {:headers => true, :return_headers => true, :header_converters => :symbol, :converters => :all} )
Pour vérifier les données, cela fonctionne:
puts tickers[1][:ticker]
ZUMZ
Cependant ce n'est pas le cas:
puts tickers[:ticker => "XTEX"][:price]
Comment pourrais-je transformer ce tableau en un hachage en utilisant le champ ticker comme clé unique, de sorte que je pourrais facilement rechercher n'importe quel autre champ associativement tel que défini dans la ligne 1 de l'entrée? Traiter avec beaucoup plus de colonnes et de lignes.
Beaucoup apprécié!
6 réponses
Comme ceci (cela fonctionne aussi avec d'autres CSV, pas seulement celui que vous avez spécifié):
require 'csv'
tickers = {}
CSV.foreach("stocks.csv", :headers => true, :header_converters => :symbol, :converters => :all) do |row|
tickers[row.fields[0]] = Hash[row.headers[1..-1].zip(row.fields[1..-1])]
end
Résultat:
{"ZUMZ"=>{:price=>30.0, :market_cap=>933.9}, "XTEX"=>{:price=>16.02, :market_cap=>811.57}, "AAC"=>{:price=>9.83, :market_cap=>80.02}}
Vous pouvez accéder aux éléments de cette structure de données comme ceci:
puts tickers["XTEX"][:price] #=> 16.02
Edit( selon le commentaire): pour sélectionner des éléments, vous pouvez faire quelque chose comme
tickers.select { |ticker, vals| vals[:price] > 10.0 }
CSV.read(file_path, headers:true, header_converters: :symbol, converters: :all).collect do |row|
Hash[row.collect { |c,r| [c,r] }]
end
Pour ajouter à la réponse de Michael Kohl, si vous voulez accéder aux éléments de la manière suivante
puts tickers[:price]["XTEX"] #=> 16.02
, Vous pouvez essayer l'extrait de code suivant:
CSV.foreach("Workbook1.csv", :headers => true, :header_converters => :symbol, :converters => :all) do |row|
hash_row = row.headers[1..-1].zip( (Array.new(row.fields.length-1, row.fields[0]).zip(row.fields[1..-1])) ).to_h
hash_row.each{|key, value| tickers[key] ? tickers[key].merge!([value].to_h) : tickers[key] = [value].to_h}
end
Pour obtenir le meilleur des deux mondes (lecture très rapide à partir D'un énorme fichier et les avantages d'un objet CSV Ruby natif), mon code a depuis évolué vers cette méthode:
$stock="XTEX"
csv_data = CSV.parse IO.read(%`|sed -n "1p; /^#{$stock},/p" stocks.csv`), {:headers => true, :return_headers => false, :header_converters => :symbol, :converters => :all}
# Now the 1-row CSV object is ready for use, eg:
$company = csv_data[:company][0]
$volatility_month = csv_data[:volatility_month][0].to_f
$sector = csv_data[:sector][0]
$industry = csv_data[:industry][0]
$rsi14d = csv_data[:relative_strength_index_14][0].to_f
Qui est plus proche de ma méthode d'origine, mais ne lit que dans un enregistrement plus la ligne 1 du fichier csv d'entrée contenant les en-têtes. Les instructions inline sed
s'en occupent-et le tout est remarquablement instantané. C'est mieux que last parce que maintenant je peux accéder à tous les champs de Ruby, et associativement, ne se souciant plus des numéros de colonne comme ce fut le cas avec awk
.
Pas comme 1-liner-ie mais c'était plus clair pour moi.
csv_headers = CSV.parse(STDIN.gets)
csv = CSV.new(STDIN)
kick_list = []
csv.each_with_index do |row, i|
row_hash = {}
row.each_with_index do |field, j|
row_hash[csv_headers[0][j]] = field
end
kick_list << row_hash
end
Bien que ce ne soit pas une solution Ruby native à 100% à la question initiale, si d'autres trébuchent ici et se demandent quel appel awk j'ai fini par utiliser pour l'instant, le voici:
$dividend_yield = IO.readlines("|awk -F, '$1==\"#{$stock}\" {print $9}' datafile.csv")[0].to_f
Où $ stock est la variable que j'avais précédemment assignée au symbole ticker d'une entreprise (le champ Clé wannabe). Survit commodément aux problèmes en retournant 0.0 si: ticker ou fichier ou champ # 9 introuvable / vide, ou si la valeur ne peut pas être typecastée à un float. Donc, tout ' % ' final dans mon cas devient bien tronquer.
Notez qu'à ce stade, on pourrait facilement ajouter plus de filtres dans awk pour avoir des e / s.readlines renvoie un tableau 1-dim de lignes de sortie du CSV résultant plus petit, par exemple.
awk -F, '$9 >= 2.01 && $2 > 99.99 {print $0}' datafile.csv
Sorties dans bash dont les lignes ont un DivYld (col 9) sur 2.01 et le prix (col 2) sur 99.99. (Malheureusement, je n'utilise pas la ligne d'en-tête pour déterminer les numéros de champ, ce qui est l'endroit où j'espérais finalement un tableau Ruby associatif consultable.)