Добавление динамического столбца данных в несколько CSV - PullRequest
0 голосов
/ 19 июня 2019

У меня есть сценарий, который загружает несколько CSV-файлов с одинаковыми именами (то есть data.csv, data (1) .csv), и я хочу добавить столбец к каждому CSV, а затем объединить их в один CSV.

Так, например,

data.csv имеет 4 заголовка (header_1, header_2 и т. Д.), И я хотел бы добавить header_5 с переменной foobar. Для data.csv foobar - это «утка», и поэтому для каждой строки в data.csv в header_5 будет столько уток.

data (1) .csv - та же самая сделка, но эта переменная времени foobar теперь "собака". И сценарий будет заполнять header_5 как можно большим количеством собак.

На последнем шаге 2 CSV будут объединены, сохраняя измененные данные, в один гигантский CSV.

Я долго об этом думал. Я не знаю много о Ruby, и такого рода проблемы являются новыми для меня, поэтому я надеюсь, что я объясню это хорошо.

Я думал о том, чтобы сначала изменить CSV, чтобы получить новый столбец данных, а затем объединить их, но я продолжаю сталкиваться с проблемой имен CSV. Чтобы изменить файлы, мне нужно знать имена, поэтому я подумал о подстановочных знаках. Но тогда как бы я различал CSV? Это, вероятно, перезапишет данные.

Мой текущий код имеет эту проблему.

 def CSV_Creation (source)


        input_files = Dir.glob("data*.csv")

        all_headers = input_files.reduce([]) do |all_headers, file|
            header_line = File.open(file, &:gets)
            all_headers | CSV.parse_line(header_line)
        end


        CSV.open("out.csv", "a+") do |out|
            all_headers << "Source"
            out << all_headers 


            input_files.each do |file|
                CSV.foreach(file, headers: true) do |row|
                    out << all_headers.map { |header| row[header] }



                end
            end
        end
    end

  • Параметр источника изменяется в зависимости от загружаемого CSV
  • Я использовал групповые символы для сбора всех CSV, собрал заголовки, чтобы добавить еще один заголовок, и сбросил все данные в новый CSV. Но, конечно, данные перезаписываются.

Я не совсем уверен, как бы сохранить данные в последнем столбце от перезаписи.

EDIT

Спасибо за все ваши ответы до сих пор. Я обновил код, который, надеюсь, будет более понятным:

def CSV_Creation (source)



        l_source = {'lead_source' => "#{source}"}

        input_file = Dir.glob("data*.csv").last



        puts "Here is " + input_file


        rows = CSV.open(input_file, headers: true).map{ |row| row.to_h }


        rows.each { |h| h.merge!(l_source)}
        headers = rows.first.keys
       rows.first.keys.each {|k| puts k}



        csv_response = CSV.generate do |csv| 
            csv << headers
            rows.each do |row|
                csv << row.values_at(*headers) 
            end
        end
        File.open("#{source}.csv", "w") {|file| file.write(csv_response)}


    end 

Это создает два разных файла CSV с соответствующим столбцом и данными. Теперь мне нужно выяснить, как объединить два файла.

ВТОРОЕ РЕДАКТИРОВАНИЕ

Вот так выглядит окончательный код. Он делает то, что я спрашиваю, поэтому я решил, что все в порядке?

 def CSV_Creation (source)



        l_source = {'lead_source' => "#{source}"}

        input_file = Dir.glob("data*.csv").last



        puts "Here is " + input_file


        rows = CSV.open(input_file, headers: true).map{ |row| row.to_h }


        rows.each { |h| h.merge!(l_source)}
        headers = rows.first.keys
       rows.first.keys.each {|k| puts k}



        csv_response = CSV.generate do |csv| 
            csv << headers
            rows.each do |row|
                csv << row.values_at(*headers) 
            end
        end
        File.open("#{source}.csv", "w") {|file| file.write(csv_response)}


        input_files = Dir.glob("#{source}*.csv")


        all_headers = input_files.reduce([]) do |all_headers, file|
            header_line = File.open(file, &:gets)
            all_headers | CSV.parse_line(header_line)
        end

        CSV.open("out.csv", "a+") do |out|
            out << all_headers 

            input_files.each do |file|
                CSV.foreach(file, headers: true) do |row|
                    out << all_headers.map { |header| row[header] }
                end
            end
        end







    end 

Большое спасибо всем, кто дал мне совет !!

Ответы [ 3 ]

1 голос
/ 19 июня 2019

У меня есть тупой способ сделать то, что вы просите:

  • Соедините строки каждого CSV-файла в файле out.csv (с небольшой безопасностью)
  • Скажите, какиеиз файла приходит столбец в source.csv
# idk what to do with source
def CSV_Creation (source)
    input_files = Dir.glob("data*.csv").map { |filename| File.open(filename) }

    headers = input_files.map(&:gets)
    # Fix for "empty" lines in data files
    line_fix = headers.map { |header| CSV.parse_line(header).map { ',' }.join }

    CSV.open("out.csv", "a+") do |out|
        # We add the header
        out.puts headers.map(&:chomp).join(',')
        # We try to read all the lines
        until (lines = input_files.map(&:gets)).concat.empty?
            out.puts lines.map.with_index do |line, index|
                line&.chomp || line_fix[index]
            end.join(',')
        end
    end

    # In order to know the names we'll store a csv associating header to the filename
    File.open('source.csv', 'w') do |f|
        f.puts headers.map(&:chomp).join(',')
        line = input_files.map.with_index do |file, index|
            ([file.path] * line_fix[index].size).to_csv
        end
        f.puts line.map(&:chomp).join(',')
    end
ensure
    input_files.each(&:close)
end
0 голосов
/ 20 июня 2019

Код

require 'csv'

def combine_csv_files(*csv_files, sep, out_file_name)
  (file_name, new_header_name), *rest = csv_files
  csv = CSV.read(file_name, headers: true, col_sep: sep)
  new_col = (1..csv.size).to_a
  csv[new_header_name] = new_col
  rest.each do |file_name, new_header_name|
    csv1 = CSV.read(file_name, headers: true, col_sep: sep)
    csv1.headers.each { |header| csv1.each { |row| csv[header] = row[header] } }
    csv[new_header_name] = new_col
  end
  CSV.open(out_file_name, "w") do |f|
    f << csv.headers
    csv.each { |row| f << row }
  end
end    

Пример

Предположим, у нас есть файлы

FNAME1 = 'dogsandcats.csv'
FNAME2 = 'cowsandpigs.csv'

содержимое которого указано в моем другом ответе, и мы хотим добавить столбцы к этим двум файлам с заголовками "col1" и "col2" соответственно и объединить эти два файла.

combine_csv_files(*[[FNAME1, "col1"], [FNAME2, "col2"]], ',', 'everything.csv') 

puts IO.read('everything.csv')
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2

Объяснение

Шаги следующие (для FNAME1 и FNAME2, как определено для примера).

csv_files = [[FNAME1, "col1"], [FNAME2, "col2"]]
sep = ','
out_file_name = 'everything.csv'

Разделить csv_files между его первым элементом и всеми остальными элементами.

(file_name, new_header_name), *rest = csv_files
  #=> [["dogsandcats.csv", "col1"], ["cowsandpigs.csv", "col2"]]
file_name
  #=> "dogsandcats.csv"
new_header_name
  #=> "col1"
rest
  #=> [["cowsandpigs.csv", "col2"]]

Процесс деления csv_files таким образом называется декомпозиция массива . Теперь прочитайте первый файл, создав объект CSV.

csv = CSV.read(file_name, headers: true, col_sep: sep)
  #=> #<CSV::Table mode:col_or_row row_count:3>

Посмотрим, что у нас есть.

csv.to_a
  #=> [["dog", "cat"], ["woof", "purr"], ["devoted", "independent"]]

Теперь добавьте столбец и посмотрите, что у нас получится.

new_col = (1..csv.size).to_a 
csv[new_header_name] = new_col
  #=> [1, 2] 
csv.to_a
  #=> [["dog", "cat", "col1"], ["woof", "purr", 1], ["devoted", "independent", 2]]

Считайте дескриптор для следующего файла CSV, затем прочитайте файл в CSV объект csv1:

file_name, new_header_name = rest.shift
  #=> ["cowsandpigs.csv", "col2"] 
csv1 = CSV.read(file_name, headers: true, col_sep: sep)
  #=> #<CSV::Table mode:col_or_row row_count:3>
csv1.to_a
  #=> [["cow", "pig"], ["moo", "oink"], ["dumb", "smart"]]

Добавить csv1 к csv:

csv1.headers.each { |header| csv[header] = csv1.map { |row| row[header] } }
  #=> ["cow", "pig"]
csv.to_a
  #=> [["dog",     "cat",         "col1", "cow",  "pig"  ],
  #    ["woof",    "purr",        1,      "moo",  "oink" ],
  #    ["devoted", "independent", 2,      "dumb", "smart"]] 

Добавить новую колонку и проверить csv:

csv[new_header_name] = new_col
  #=> [1, 2]
csv.to_a
  #=> [["dog",     "cat",         "col1", "cow",  "pig",   "col2"],
  #    ["woof",    "purr",        1,      "moo",  "oink",  1     ],
  #    ["devoted", "independent", 2,      "dumb", "smart", 2     ]]

Осталось только записать csv в файл.

CSV.open(out_file_name, "w") do |f|
  f << csv.headers
  csv.each { |row| f << row }
end
  #> #<CSV::Table mode:col_or_row row_count:3>

Давайте посмотрим на содержимое только что написанного файла:

puts IO.read(out_file_name)
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2
0 голосов
/ 19 июня 2019

Поскольку строки заголовка должны эффективно обрабатываться, как и все остальные строки, существует значительное преимущество для использования методов класса CSV для выполнения этой задачи. (Примечание: я опубликовал этот ответ перед подготовкой второго ответа, в котором используются методы CSV. Несмотря на то, что я предпочитаю свой другой ответ, я решил оставить этот.)

Код

def combine_csv_files(*csv_files, sep, out_file_name)
  IO.write(out_file_name,
    csv_files.each_with_object([]) do |(file_name, new_header_name), arr|
      a = IO.readlines(file_name, chomp: true)
      arr.concat(
        a.map { |line| line.split(sep) }.
          transpose << [new_header_name, *(1..a.size-1).to_a]
      )
    end.transpose.
        map { |a| a.join(sep) }.
        join("\n")
  )
end

Пример

Давайте сначала создадим два файла CSV 1 .

str =<<-DA_END
dog,cat
woof,purr
devoted,independent
DA_END

FNAME1 = 'dogsandcats.csv'
IO.write(FNAME1, str)
  #=> 38

str =<<-DA_END
cow,pig
moo,oink
dumb,smart
DA_END

FNAME2 = 'cowsandpigs.csv'
IO.write(FNAME2, str)
  #=> 28

Предположим, мы хотим добавить столбцы к этим двум файлам с заголовками "col1" и "col2" соответственно. Тогда

combine_csv_files(*[[FNAME1, "col1"], [FNAME2, "col2"]], ',', 'everything.csv') 

puts IO.read('everything.csv')
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2

Объяснение

Мы можем выполнить вычисления. Пусть

csv_files = [[FNAME1, "col1"], [FNAME2, "col2"]]
sep = ','
out_file_name = 'everything.csv'

Первым шагом является создание перечислителя.

enum = csv_files.each_with_object([])
  #=> #<Enumerator: [["dogsandcats.csv", "col1"], ["cowsandpigs.csv", "col2"]]:
      # each_with_object([])>

См. Enumerable # each_with_object 2 . Это создает перечислитель. Метод Enumerator # next используется для генерации перечислителем элементов, которые передаются в блок и присваиваются переменным блока.

(file_name, new_header_name), arr = enum.next
  #=> [["dogsandcats.csv", "col1"], []] 
file_name
  #=> "dogsandcats.csv" 
new_header_name
  #=> "col1" 
arr
  #=> [] 

Процесс деления элемента, возвращаемого enum.next, на компоненты, назначенные блочным переменным, называется декомпозиция массива . Теперь выполним расчет блока.

b = IO.readlines(file_name, chomp: true)
  #=> ["dog,cat", "woof,purr", "devoted,independent"]
c = b.map { |line| line.split(sep) }
  #=> [["dog", "cat"], ["woof", "purr"], ["devoted", "independent"]] 
d = c.transpose
  #=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"]] 
e = d << [new_header_name, *(1..b.size-1).to_a] 
  #=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]] 

См. IO :: readlines 3 , Enumerable # map и Array # transpose . Продолжая с вычислением блока,

arr.concat(e)
  #=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]] 

См. Array # concat . Теперь перечислитель генерирует второй элемент, который передается в блок, переменным блока присваиваются значения и выполняется расчет блока.

(file_name, new_header_name), arr = enum.next
  #=> [["cowsandpigs.csv", "col2"], []] 
file_name
  #=> "cowsandpigs.csv" 
new_header_name
  #=> "col2" 
arr
  #=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]] 

Обратите внимание, что arr был обновлен.

b = IO.readlines(file_name, chomp: true)
  #=> ["cow,pig", "moo,oink", "dumb,smart"] 
c = b.map { |line| line.split(sep) }
  #=> [["cow", "pig"], ["moo", "oink"], ["dumb", "smart"]] 
d = c.transpose
  #=> [["cow", "moo", "dumb"], ["pig", "oink", "smart"]] 
e = d << [new_header_name, *(1..b.size-1).to_a] 
  #=> [["cow", "moo", "dumb"], ["pig", "oink", "smart"], ["col2", 1, 2]] 
arr.concat(e)
  #=> [["dog",  "woof", "devoted"    ],
  #    ["cat",  "purr", "independent"],
  #    ["col1", 1,      2],
  #    ["cow",  "moo",  "dumb"       ],
  #    ["pig",  "oink", "smart"      ],
  #    ["col2", 1,      2            ]] 

Теперь enum пытается создать другой элемент.

enum.next
  #=> StopIteration (iteration reached an end)

Это исключение вызывает возврат arr. Продолжая,

f = arr.transpose
  #=> [["dog",     "cat",         "col1", "cow",  "pig",   "col2"],
  #    ["woof",    "purr",        1,      "moo",  "oink",  1     ],
  #    ["devoted", "independent", 2,      "dumb", "smart", 2]    ] 
g = f.map { |a| a.join(',') }
  #=> ["dog,cat,col1,cow,pig,col2",
  #    "woof,purr,1,moo,oink,1",
  #    "devoted,independent,2,dumb,smart,2"] 
h = g.join("\n")
  #=> "dog,cat,col1,cow,pig,col2\nwoof,purr,1,moo,oink,1\ndevoted,independent,2,dumb,smart,2" 
IO.write(out_file_name, h)
  #=> 83

См. IO :: write .

Давайте подтвердим, что получили желаемый результат.

puts IO.read(out_file_name)
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2

См. IO :: read .

1. Обратите внимание, что следующие два heredoc были отступом 4 пробела. Если кто-то хочет запустить этот код, сначала он должен быть удален.

2. Знак фунта в Enumerable#each_with_object указывает, что each_with_object является методом экземпляра. Напротив, двойное двоеточие в IO::readlines указывает, что readlines является (классом) метод класса IO.

3. IO методы и методы экземпляра часто пишутся с File, а не IO, в качестве получателя (например, File.write(fname)). Это разрешено, потому что File является подклассом IO (File.superclass #=> IO) и поэтому наследует его методы и методы экземпляра.

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