Как посчитать ответы «да» (строки с определенным значением в определенном столбце) в файле CSV? - PullRequest
1 голос
/ 27 апреля 2019

У меня есть сводка, и я хочу посчитать количество «Да» и «Нет» для каждого предложения:

Name | Cats| Dogs| Rabbits|
john | Yes | No  | No    |
max  | No  | No  | Yes   |
oli  | Yes | Yes | Yes   |

Как я могу получить содержимое столбцов?cats -> 2, dogs -> 1, rabbits -> 2

Итак, я начинаю с чего-то вроде этого:

CSV.parse("summary.csv", header: true, col_sep: ";") do |row|
  "Cats"   => row[0]
  "Dogs"   => row[1] 
  "Rabbits" => row[2]
end

Ответы [ 4 ]

1 голос
/ 27 апреля 2019
csv =<<-END
Name | Cats| Dogs| Rabbits|
john | Yes | No  | No    |
max  | No  | No  | Yes   |
oli  | Yes | Yes | Yes   |
END

FNAME = 'temp.csv'
File.write(FNAME, csv)
  #=> 109

Использовать методы CSV

Мы могли бы использовать CSV методы следующим образом.

require 'csv'

csv = CSV.read(FNAME, headers: true, col_sep: '|')
csv.headers.each_with_object({}) do |animal,h|
  unless animal.nil? || animal.strip == "Name"
    h[animal.strip.downcase] = csv[animal].count { |s| s.strip == "Yes" }
  end
end
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

Использовать CSV методы после предварительной обработки файла

Использование CSV методов здесь немного громоздко. Для одного:

csv.headers
  #=> ["Name ", " Cats", " Dogs", " Rabbits", nil] 

Элемент nil существует, потому что разделитель равен |, и этот символ появляется в конце каждой строки. Вторая проблема - это наличие пробелов. Например, было бы удобнее, если бы метка столбца " Cats" была "Cats" или, еще лучше, "cats".

Ввиду этих сложностей можно подумать о некоторой простой предварительной обработке файла, чтобы упростить применение CSV методов.

TEMP_FNAME = 'temp1.csv'    
File.write(TEMP_FNAME, File.read(FNAME).delete(' ').downcase.gsub(/\|$/,''))
  #=> 68

Посмотрим, что было написано.

puts File.read(TEMP_FNAME)
name|cats|dogs|rabbits
john|yes|no|no
max|no|no|yes
oli|yes|yes|yes

Теперь мы можем довольно легко создать желаемый хеш.

csv = CSV.read(TEMP_FNAME, headers: true, col_sep: '|')
csv.headers.each_with_object({}) do |animal,h|
  h[animal] = csv[animal].count("yes") unless animal == 'name'
end
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

Возможно, выполнение этого в два этапа также упрощает отладку и тестирование.

Рассматривать файл как обычный текстовый файл

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

File.read(FNAME).downcase.split("\n").
     map { |line| line.split(/ *\| */)[1..] }.transpose.
     each_with_object({}) { |(lbl,*rest),h| h[lbl]=rest.count('yes') }
       #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

Шаги следующие.

a = File.read(FNAME).downcase.split("\n")
puts a
name | cats| dogs| rabbits|
john | yes | no  | no    |
max  | no  | no  | yes   |
oli  | yes | yes | yes   |

b = a.map { |line| line.split(/ *\| */)[1..] }
  #=> [["cats", "dogs", "rabbits"],
  #    ["yes", "no", "no"],
  #    ["no", "no", "yes"],
  #    ["yes", "yes", "yes"]] 
c = b.transpose
  #=> [["cats", "yes", "no", "yes"],
  #    ["dogs", "no", "no", "yes"],
  #    ["rabbits", "no", "yes", "yes"]] 
c.each_with_object({}) { |(lbl,*rest),h| h[lbl]=rest.count('yes') }
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2} 
1 голос
/ 27 апреля 2019

Это не очень элегантное решение, но оно выполняет работу "классическим" (итеративно запрограммированным) способом.Я удалил его из Rails, так что это отдельное приложение.Вы должны будете положить свой Rails-Stuff обратно.

Обратите внимание, что в вашем примере CSV используется "|"(канал) в качестве разделителя.

require 'csv'

# Start with 0 of each
counts = {cats: 0, dogs: 0, rabbits: 0}
# run over every row
CSV.foreach("c.csv", headers: true, col_sep: "|") do |row|
  # Check answers in each column and increase count if "Yes"
  if row[1].strip == 'Yes'                                         
    counts[:cats] = counts[:cats] + 1                              
  end                                                              
  if row[2].strip == 'Yes'                                         
    counts[:dogs] = counts[:dogs] + 1                              
  end                                                              
  if row[3].strip == 'Yes'                                         
    counts[:rabbits] = counts[:rabbits] + 1                        
  end                                                              
end                                                                

puts counts # Will print {:cats=>2, :dogs=>1, :rabbits=>2}

Обратите внимание, что CSV-доступ может быть значительно упрощен / сделан более читабельным во многих отношениях (см. https://ruby -doc.org / stdlib-2.00,0 / libdoc / CSV / RDoc / CSV.html ).Подсчет и группировка, которые в примере кода выполняются путем ручного обхода каждой строки и столбца, могут быть значительно упрощены и улучшены с помощью методов из модуля Enumerable: https://ruby -doc.org / core-2.6.2/Enumerable.html.Чтение и понимание данных ссылок значительно улучшит вашу производительность программирования.

Удачи в изучении и взломе!

0 голосов
/ 27 апреля 2019

Сначала сопоставьте данные в table:

require 'csv'

raw_table = File.read("summary.csv")
table = CSV.parse(raw_table.gsub(' ', ''), col_sep: '|', headers: true)

. При этом данные считываются в строку, удаляются пробелы и выполняется синтаксический анализ в CSV с разделителем и заголовками.

Разделитель столбцовравен '|', поскольку в крайнем правом углу находится разделитель, он генерирует пустой столбец.Затем необходимо удалить его, см. .compact или [1...-1], используемые здесь ниже.


Затем, один из вариантов os для обработки таблицы как массива хэшей:
h_table = table.map { |e| e.to_h.compact }
#=> [{"Name"=>"John", "Cats"=>"Yes", "Dogs"=>"No", "Rabbits"=>"No"}, {"Name"=>"Max", "Cats"=>"No", "Dogs"=>"No", "Rabbits"=>"Yes"}, {"Name"=>"Oli", "Cats"=>"Yes", "Dogs"=>"Yes", "Rabbits"=>"Yes"}]

Установите counts в ноль, а затем отсканируйте h_table:

counts = h_table.first.transform_values { |v| 0 }.tap{ |h| h.delete 'Name'}
counts #=> {"Cats"=>0, "Dogs"=>0, "Rabbits"=>0}
counts.keys.each { |k| h_table.each { |h| counts[k] += 1 if h[k] == 'Yes' } }
counts #=> {"Cats"=>2, "Dogs"=>1, "Rabbits"=>2}


Или (лучший вариант) преобразовать в массив и обработать его транспонированным, как указано в @fphilipe:
table.to_a.transpose[1...-1].each_with_object({}) { |(k, *v), h| h[k] = v.count{ |e| e == 'Yes' } }
#=> {"Cats"=>2, "Dogs"=>1, "Rabbits"=>2}


Но если исходный ввод равен
Name;Cats;Dogs;Rabbits
John;Yes;No ;No
Max;No ;No ;Yes
Oli;Yes;Yes;Yes

Просто используйте table = CSV.read("summary.csv", col_sep: ';', headers: true), затем примените один из упомянутых методов, , просто не удаляя пустой столбец .

0 голосов
/ 27 апреля 2019

Это будет делать:

CSV.open("summary.csv", col_sep: ";")
  .to_a
  .transpose[1..]
  .map { |(name, *data)| [name, data.count { |val| val == "Yes" }] }
  .to_h

Вывод:

{
  "Cats" => 2,
  "Dogs" => 1,
  "Rabbits" => 2
}

Пошаговое объяснение:

Сначала давайте прочитаем данные:

irb> CSV.open("x.csv", col_sep: ";").to_a
=> [["Name", "Cats", "Dogs", "Rabbits"], ["john", "Yes", "No", "No"], ["max", "No", "No", "Yes"], ["oli", "Yes", "Yes", "Yes"]]

Далее, перенести, чтобы животные и ценности были вместе:

irb> CSV.open("x.csv", col_sep: ";").to_a.transpose
=> [["Name", "john", "max", "oli"], ["Cats", "Yes", "No", "Yes"], ["Dogs", "No", "No", "Yes"], ["Rabbits", "No", "Yes", "Yes"]]

Обратите внимание, что первый элемент не имеет значения, поэтому мы игнорируем его, добавляя [1..], чтобы получить все элементы, кроме первого.

Тогда нам просто нужно преобразовать каждый элемент массива в имя животного и количество "Yes" значений. Для одного элемента мы можем сделать это следующим образом:

row = ["Cats", "Yes", "No", "Yes"]
(name, *data) = row

name будет "Cats", а data будет содержать другие элементы, т.е. будет ["Yes", "No", "Yes"].

Теперь нам нужно просто подсчитать "Yes" значения в data:

irb> [name, data.count { |val| val == "Yes" }]
=> ["Cats", 2]

Используя map, мы можем сделать это для всех элементов транспонированного набора данных. Вызов #to_h для этого будет использовать первый элемент каждого массива в качестве ключа и второй элемент в качестве значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...