将动态数据列追加到多个CSV

时间:2019-06-19 12:57:50

标签: ruby csv

我有一个脚本,可以下载多个具有相似名称(即data.csv,data(1).csv)的CSV,并且我想在每个CSV后面附加一列,然后将它们组合成一个CSV。

例如,

data.csv有4个标头(header_1,header_2等),我想用变量foobar添加header_5。对于data.csv,foobar是“鸭子”,因此对于data.csv的每一行,header_5都会有那么多鸭子。

data(1).csv是相同的处理,但是这次变量foobar现在是“ dog”。而且该脚本将用很多狗填充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中。但是当然,数据会被覆盖。

我不确定我如何确保最后一列中的数据不会被覆盖。

编辑

感谢您到目前为止的所有答复。我更新了代码,希望能使它更有意义:

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 个答案:

答案 0 :(得分:1)

我有一种愚蠢的方式来执行您的要求:

  • 在out.csv文件中加入每个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

答案 1 :(得分:0)

由于要像对待所有其他行一样有效地处理标题行,因此使用类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返回的元素划分为分配给块变量的组件的过程称为array decomposition。现在,我们执行块计算。

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#mapArray#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。请注意,以下两个heredocs已缩进4个空格。如果希望运行此代码,则应先使其缩进。

2。 Enumerable#each_with_object中的井号表示each_with_object是实例方法。相比之下,IO::readlines中的双冒号表示readlinesIO类的(类)方法。

3。 IO方法和实例方法通常使用File而不是IO作为接收者(例如File.write(fname))来编写。之所以允许这样做是因为FileIOFile.superclass #=> IO)的子类,因此继承了它的方法和实例方法。

答案 2 :(得分:0)

代码

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

说明

步骤如下(对于示例中定义的FNAME1FNAME2)。

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的过程称为array decomposition。现在,读取第一个文件,创建一个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