importar desde CSV a la matriz Ruby, con el primer campo como clave hash, luego buscar el valor de un campo dada la fila de encabezado
arrays stocks (6)
Tal vez alguien pueda ayudarme.
Comenzando con un archivo CSV como ese:
Ticker,"Price","Market Cap"
ZUMZ,30.00,933.90
XTEX,16.02,811.57
AAC,9.83,80.02
Me las arreglo para leerlos en una matriz:
require ''csv''
tickers = CSV.read("stocks.csv", {:headers => true, :return_headers => true, :header_converters => :symbol, :converters => :all} )
Para verificar los datos, esto funciona:
puts tickers[1][:ticker]
ZUMZ
Sin embargo, esto no:
puts tickers[:ticker => "XTEX"][:price]
¿Cómo voy a convertir esta matriz en un hash usando el campo ticker como clave única, de modo que pueda buscar fácilmente cualquier otro campo asociativamente como se define en la línea 1 de la entrada? Tratando con muchas más columnas y filas.
¡Muy apreciado!
Así (también funciona con otros CSV, no solo con el que especificó):
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
Resultado:
{"ZUMZ"=>{:price=>30.0, :market_cap=>933.9}, "XTEX"=>{:price=>16.02, :market_cap=>811.57}, "AAC"=>{:price=>9.83, :market_cap=>80.02}}
Puede acceder a los elementos en esta estructura de datos de esta manera:
puts tickers["XTEX"][:price] #=> 16.02
Editar (según comentario): para seleccionar elementos, puede hacer algo como
tickers.select { |ticker, vals| vals[:price] > 10.0 }
No como 1-liner, es decir, pero esto fue más claro para mí.
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
Para agregar a la respuesta de Michael Kohl, si desea acceder a los elementos de la siguiente manera
puts tickers[:price]["XTEX"] #=> 16.02
Puedes probar el siguiente fragmento de código:
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
Para obtener lo mejor de ambos mundos (lectura muy rápida de un archivo enorme Y los beneficios de un objeto Ruby CSV nativo), mi código se había convertido en este método:
$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
que está más cerca de mi método original, pero solo lee en un registro más la línea 1 del archivo de entrada csv que contiene los encabezados. Las instrucciones en línea sed
se encargan de eso, y todo es notoriamente instantáneo. Esto es mejor que el last porque ahora puedo acceder a todos los campos desde Ruby, y de forma asociativa, sin preocuparme más por los números de las columnas como en el caso de awk
.
Si bien esta no es una solución de Ruby 100% nativa a la pregunta original, si otros tropezaran aquí y se preguntaran qué tipo de llamada de awk utilicé por ahora, aquí está:
$dividend_yield = IO.readlines("|awk -F, ''$1==/"#{$stock}/" {print $9}'' datafile.csv")[0].to_f
donde $ stock es la variable que había asignado previamente al símbolo de una empresa (el campo clave wannabe). Resiste convenientemente los problemas al devolver 0,0 si: ticker o archivo o campo # 9 no encontrado / vacío, o si el valor no se puede escribir en un flotador. Entonces cualquier ''%'' posterior en mi caso se truncará bien.
Tenga en cuenta que en este punto uno podría agregar fácilmente más filtros dentro de awk para que IO.readlines devuelva una matriz de 1 dim de líneas de salida del CSV resultante más pequeño, por ejemplo.
awk -F, ''$9 >= 2.01 && $2 > 99.99 {print $0}'' datafile.csv
salidas en bash cuyas líneas tienen un DivYld (col 9) sobre 2.01 y precio (col 2) sobre 99.99. (Lamentablemente, no estoy usando la fila de encabezado para determinar los números de campo, que es donde finalmente esperaba una matriz de Ruby asociativa de búsqueda.)
CSV.read(file_path, headers:true, header_converters: :symbol, converters: :all).collect do |row|
Hash[row.collect { |c,r| [c,r] }]
end