我有一个脚本,可以下载多个具有相似名称(即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
我不确定我如何确保最后一列中的数据不会被覆盖。
编辑
感谢您到目前为止的所有答复。我更新了代码,希望能使它更有意义:
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
非常感谢给我建议的每个人!
答案 0 :(得分:1)
我有一种愚蠢的方式来执行您的要求:
# 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#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。请注意,以下两个heredocs已缩进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
)的子类,因此继承了它的方法和实例方法。
答案 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
说明
步骤如下(对于示例中定义的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
的过程称为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