SAS -- 读取多个 csv 文件的第一个和最后一个观测值

SAS -- Read first and last observation of multiple csv files

提问人:Johannes Bleher 提问时间:6/29/2017 更新时间:7/1/2017 访问量:1548

问:

我想读取存储在 Linux 计算机上一个文件夹中的大量.csv文件(几千兆字节)的第一条和最后一条记录。假设他们被召唤,依此类推。have1.csv, have2.csv, ...

所以我尝试了以下代码,它只给了我第一行。但不是最后一行。

%let datapath = ~/somefolder/;    
data want;

length finame $300.;
/*Reference all CSV files in input data folder*/
infile "&datapath.have*.csv" delimiter="," 
        MISSOVER DSD lrecl=32767 firstobs=2 
        eov=eov eof=eof filename=finame end=done;

/*Define input format of variables*/
informat Var1 COMMA. Var2 COMMA. Var3 COMMA.;
/*Loop over files*/
do while(not done);

    /*Set trailing @ to hold the input open for the next input statement 
      this is because we have several files */
    input @;

    /*If first line in file is encountered eov is set to 1,
      however, we have firstobs=2, hence all lines would be skipped. 
      So we need to reset EOV to 0.*/
    if eov then
    do;
        /*Additional empty input statement 
         handles missing value at first loop*/
        input;
        eov = 2;
    end;
    /*First observation*/
    if eov=2 then do;
        input Var1--Var3;
        fname=finame;
        output;
        eov = 0;
    end;

        /*Last observation*/
       if 0 then do;
          eof:      input Var1--Var3;
                    fname=finame;
                    output;
        end;
        input;

end;
stop;

run;

非常感谢您的帮助!如果我误解了 infile、end、eov、eof 和 input @ 的概念或相互作用,请告诉我!我不知道我的错误在哪里......

CSV 输入 文件-IO SAS EOF

评论

1赞 Tom 6/30/2017
您是否也想跳过标题行?这就是关于 FIRSTOBS= 选项的评论吗?
0赞 Johannes Bleher 6/30/2017
是的,很抱歉没有早点回信。

答:

1赞 vasja 6/29/2017 #1

这似乎对我有用,请尝试一下:

data want;

length finame $300.;
/*Reference all CSV files in input data folder*/
infile "E:\temp\test\have*.txt" delimiter="," 
        MISSOVER DSD lrecl=32767
        eov=eov filename=finame end=done;

        /* Note: firstobs option seems to work on first file only */

/*Define input format of variables*/
informat Var1 COMMA. Var2 COMMA. Var3 COMMA.;

input; /* skip header in first file */

input Var1--Var3; /* read first real record in first file */
fname=finame;
output;

/* Loop over files*/
do while(not done);

   input @;/* try input do determine eov condition */

   if eov then do;/* new file detected - we're on header record, but variables contain values from previous record - see "read values" */
      output; /* variables contain values from previous record - output those values */
      input; /* skip header */
      eov = 0;
      input Var1--Var3; /* read first real observation */
      fname=finame;
      output; /* first line of new file */
   end;

    input Var1--Var3; /* read values - it might be last record */
end;
output; /* output last record of last file */
run;

实际上,正如下面的 Tom 所描述的,不需要 while 循环(危险的东西 :-))。 我现在已经修改了代码: (需要添加 RETAIN,因为我们在数据步骤本身中循环)

data want;

length finame $300.;
/*Reference all CSV files in input data folder*/
infile "E:\temp\test\have*.txt" delimiter="," 
        MISSOVER DSD lrecl=32767
        eov=eov filename=finame end=done;

informat Var1 COMMA. Var2 COMMA. Var3 COMMA.;
retain Var1 Var2 Var3 fname;
if _N_ = 1 then do; /* first file */
   input; /* skip header in first file */
   input Var1--Var3; /* read first real record in first file */
   fname=finame;
   output;
end; 

input @; /* try input do determine eov condition */

if eov then do; /* new file detected - we've moved past header record, but variables contain values from previous record - see "read values" */
   output; /* variables contain values from previous record - output those values */
   input; /* skip header */
   eov = 0;
   input Var1--Var3; /* read first real observation */
   fname=finame;
   output; /* first line of new file */
end;
else input Var1--Var3;
if done then output;
run;

评论

0赞 Johannes Bleher 6/30/2017
这行得通,太好了。我没有想到输入每一行,但只输出最后一行的诀窍......做得好!谢谢!:D
0赞 Johannes Bleher 7/3/2017
不好意思。我不能给出两个答案。汤姆是对的。他的代码更清晰一些。但你的也很好用。我被撕裂了......感谢你们的大力帮助!:D
2赞 Tom 6/30/2017 #2

如果要在 INFILE 语句中使用通配符,则可以使用 EOV= 选项创建一个变量,该变量将在新文件启动时进行标记。请注意,您需要手动重置 EOV 标志。

在读取值之前读取并按住该行,以便可以测试新文件是否已启动。这样,您就可以从上一个文件输出最后一行。您还需要保留输入变量,以便上一个文件最后一行的值可用。

您还需要使用 END= 选项才能输出最后一个文件的最后一行。

例:

data want ;
  retain filename str;
  length fname filename $200 ;
  infile '/dir1/file*' filename=fname eov=eov end=eof truncover ;
  input @;
  if eov then output;
  filename=fname ;
  input str $30. ;
  if _n_=1 or eov or eof then output;
  eov=0;
run;

输出示例:

Obs    filename       str
 1     /dir1/file1    Line1
 2     /dir1/file1    Line3
 3     /dir1/file2    Line1
 4     /dir1/file2    line4
 5     /dir1/file3    Line1
 6     /dir1/file3    Line3

如果要跳过每个文件的第一行(标题行),请在语句后添加此语句。input @;

if _n_=1 or eov then input;

请注意,如果您的输入文件可能并非都至少有两条数据行(三行计数标题行),则需要调整逻辑。

评论

1赞 Johannes Bleher 6/30/2017
这是一个很好的答案,如果我的 CSV 文件不包含带有变量名称的标题行。不幸的是,我的...很抱歉说得不够清楚。但是:有没有办法跳过每个文件中的第一个观察结果,以便 PDV 不会从第一行的标头信息接收输入?在这种情况下,我认为您使用 retain 语句的解决方案实际上会起作用......
0赞 vasja 6/30/2017
end= options 似乎只捕获最后一个文件的最后一行。无论如何,您证明了不需要 while 循环,谢谢。
0赞 Tom 7/1/2017
跳过标题行并不难。使用 EOV 标志了解何时需要跳过。
0赞 Tom 7/1/2017
是的。END 和 FIRSTOBS 选项将应用于聚合文件,而不是单个文件。要单独处理各个文件,请从文件名列表开始,并使用 FILEVAR= 选项动态指定要读取的文件名。然后,您可以使用 FIRSTOBS= 和 END= 选项来查找第一条和最后一条记录。
1赞 Tom 7/1/2017 #3

如果您有文件列表,则代码会更清晰。例如,如果可以使用 PIPE 引擎,则可以使用 ls(或 Dir)命令来获取文件名。然后使用 FILEVAR= 选项动态读取每个单独的文件。

data want ;
  infile 'ls ~/test/dir1/file*' pipe truncover ;
  input fname $200.;
  filename=fname;
  infile csv filevar=fname dsd truncover firstobs=2 end=eof ;
  do _n_=1 by 1 while (not eof);
     input str :$30. ;
     if _N_=1 or eof then output;
  end;
run;

或者,如果文件很大,则可以使用 PIPE 命令来查找每个文件的开头和结尾,而无需让 SAS 读取整个文件。您可能需要进行测试,看看它是否真的提高了性能。headtail

data want ;
  infile 'ls ~/test/dir1/file*' pipe truncover ;
  input filename $200.;
  length cmd1 cmd2 $200 ;
  cmd1='head -2 '||filename ;
  infile top pipe filevar=cmd1 dsd truncover firstobs=2 end=eof1 ;
  if  (not eof1) then do;
     input str :$30. ;
     output;
  end;
  cmd2='tail -1 '||filename ;
  infile bottom pipe filevar=cmd2 dsd truncover firstobs=1 end=eof2;
  if  (not eof2) then do;
     input str :$30. ;
     output;
  end;
run;

评论

0赞 Johannes Bleher 7/3/2017
头部和尾部解决方案并不快。慢得多。必须使用 find 命令进行调整才能生成列表......"cd ~/thepath; find . type -f -name ""*.csv"" -print"