Импорт из CSV в массив Ruby, с 1-м полем в качестве хеш-ключа, затем поиск значения поля по заданной строке заголовка

Может быть, кто-нибудь может мне помочь.

Начиная с CSV-файла, вот так:

Ticker,"Price","Market Cap"
ZUMZ,30.00,933.90
XTEX,16.02,811.57
AAC,9.83,80.02

Мне удается прочитать их в массив:

require 'csv'
tickers = CSV.read("stocks.csv", {:headers => true, :return_headers => true, :header_converters => :symbol, :converters => :all} )

Чтобы проверить данные, это работает:

puts tickers[1][:ticker]
ZUMZ

Однако это не так:

puts tickers[:ticker => "XTEX"][:price]

Как мне превратить этот массив в хеш, используя поле тикера в качестве уникального ключа, чтобы я мог легко найти любое другое поле ассоциативно, как определено в строке 1 ввода? Работа со многими другими столбцами и строками.

Очень признателен!

7 ответов

Решение

Чтобы получить лучшее из обоих миров (очень быстрое чтение из огромного файла И преимущества нативного объекта Ruby CSV), мой код с тех пор превратился в этот метод:

$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

который ближе к моему первоначальному методу, но читает только в одной записи плюс строка 1 входного CSV-файла, содержащего заголовки. Встроенный sed инструкции позаботятся об этом - и все это заметно мгновенно. Это лучше, чем в прошлом, потому что теперь я могу получить доступ ко всем полям из Ruby, и ассоциативно, больше не заботясь о номерах столбцов, как в случае с awk,

Вот так (он работает и с другими CSV, а не только с тем, который вы указали):

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

Результат:

{"ZUMZ"=>{:price=>30.0, :market_cap=>933.9}, "XTEX"=>{:price=>16.02, :market_cap=>811.57}, "AAC"=>{:price=>9.83, :market_cap=>80.02}}

Вы можете получить доступ к элементам в этой структуре данных следующим образом:

puts tickers["XTEX"][:price] #=> 16.02

Редактировать (согласно комментарию): Для выбора элементов вы можете сделать что-то вроде

 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
      CSV.read(file_path, headers:true, header_converters: :symbol, converters: :all).collect do |row|
  row.to_h
end

Чтобы добавить к ответу Майкла Коля, если вы хотите получить доступ к элементам следующим образом

puts tickers[:price]["XTEX"] #=> 16.02

Вы можете попробовать следующий фрагмент кода:

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

Не как 1-вкладыш, то есть, но это было более ясно для меня.

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

Хотя это не на 100% нативное решение Ruby по сравнению с первоначальным вопросом, если другие споткнутся здесь и задаются вопросом, какой вызов awk я использовал сейчас, вот оно:

$dividend_yield = IO.readlines("|awk -F, '$1==\"#{$stock}\" {print $9}' datafile.csv")[0].to_f

где $ stock - это переменная, которую я ранее присвоил тикерному символу компании (поле ключа подражателя). Удобно переживает проблемы, возвращая 0.0, если: тикер или файл или поле #9 не найдено / пусто, или если значение не может быть передано типу с плавающей запятой. Таким образом, любой трейлинг "%" в моем случае будет аккуратно обрезан.

Обратите внимание, что в этот момент можно легко добавить больше фильтров в awk, чтобы IO.readlines возвращали 1-мерный массив выходных строк из меньшего результирующего CSV, например.

 awk -F, '$9 >= 2.01  &&  $2 > 99.99  {print $0}' datafile.csv 

выводит в bash, какие строки имеют DivYld (столбец 9) более 2,01 и цену (столбец 2) более 99,99. (К сожалению, я не использую строку заголовка для определения номеров полей, и именно поэтому я в конечном итоге надеялся на некоторый ассоциативный массив Ruby с возможностью поиска.)

Другие вопросы по тегам