【问题标题】:Aggregating Over Actual Year in SAS在 SAS 中汇总实际年份
【发布时间】:2016-01-05 10:28:07
【问题描述】:

假设我们有下表(“购买”):

Date                 Units_Sold             Brand       Year
18/03/2010                5                   A         2010
12/04/2010                2                   A         2010
22/05/2010                1                   A         2010
25/05/2010                7                   A         2010
11/08/2011                5                   A         2011
12/07/2010                2                   B         2010
22/10/2010                1                   B         2010
05/05/2011                7                   B         2011

对于不同的品牌,同样的逻辑一直持续到 2014 年底。

我想要做的是计算每年每个品牌的 Units_Sold 数量。但是,我不想针对日历年执行此操作,而是针对实际年份执行此操作。

这是我想要的一个例子:

proc sql;
create table Dont_Want as
select Year, Brand, sum(Units_Sold) as Unit_per_Year
from Purchases
group by Year, Brand;
quit;

如果我们知道上面的逻辑是可以的,例如品牌“A”存在于整个 2010 年。但如果品牌“A”于 2010 年 3 月 18 日首次出现,并且一直存在到现在,那么 2010 年和 2011 年的比较就不够好,因为 2010 年我们“缺少” 3 个月。

所以我要做的是计算:

对于 A:从 18/03/2010 到 17/03/2011,然后从 18/03/2011 到 17/03/2012,等等。

对于 B:从 2010 年 7 月 12 日到 2011 年 7 月 11 日的总和,等等。

所有品牌的等等。

有没有一种聪明的方法来做到这一点?

【问题讨论】:

  • 您的问题不清楚。你想要的方法和想要的方法有什么区别?
  • don't want 方法中,我将得到的结果是历年(例如 2010 年)的总 Units_Sold。在 want 方法中,我想计算自品牌首次出现在数据集以来全年的总 Units_Sold。品牌的全年不限于 01/01/2010 - 31/12/2010,而是可能是 18/03/2010 - 17/03/2011。这有意义吗?
  • 那么在这种情况下什么构成一年?从出现之日起算365天?
  • 闰年呢?
  • @Joe,很好,我没有想到这一点(尽管 2016 年是闰年!)。但是,出于我学习的需要,我怀疑有一天会有所作为。但在学术环境中,您的添加是 100% 有效的。

标签: sas aggregate aggregate-functions period


【解决方案1】:

第 1 步:确保您的数据集按品牌和日期排序或索引

proc sort data=want;
     by brand date;
run;

第 2 步:计算每个产品的开始/结束日期

以下代码背后的想法:

  1. 我们知道品牌在排序数据集中的第一次出现是品牌推出的日期。我们称之为Product_Year_Start

  2. intnx 函数可用于将该日期增加 365 天,然后从中减去 1。我们称这个日期为Product_Year_End

  3. 由于我们现在知道产品的年终日期,因此我们知道,如果任何给定行的日期超过产品的年终日期,我们就会开始下一个产品年。我们将只计算该品牌的Product_Year_EndProduct_Year_Start 并将它们提高一年。

这都是使用按组处理和retain 语句实现的。

data Comparison_Dates;
    set have;
    by brand date;

    retain Product_Year_Start Product_Year_End;

    if(first.brand) then do;
        Product_Year_Start = date;
        Product_Year_End = intnx('year', date, 1, 'S') - 1;
    end;

    if(Date > Product_Year_End) then do;
        Product_Year_Start = intnx('year', Product_Year_Start, 1, 'S');
        Product_Year_End = intnx('year', Product_Year_End, 1, 'S');
    end;

    format Product_Year_Start Product_Year_End date9.;
run;

第 3 步:使用原始 SQL 代码,按新产品的开始/结束日期进行分组

proc sql;
    create table want as
    select catt(year(Product_Year_Start), '-', year(Product_Year_End) ) as Product_Year
         , Brand
         , sum(Units_Sold) as Unit_per_Year
    from Comparison_Dates
    group by Brand, calculated Product_Year
    order by Brand, calculated Product_Year;
quit;

【讨论】:

  • 感谢您抽出宝贵时间整理您的答案,对于没有遇到您代码中某些功能的人来说,这肯定是有帮助且易于理解的,例如国际化。
【解决方案2】:

以下代码按字面意思执行您所要求的操作,对于每个“品牌”的最早“日期”,它开始聚合“销售单位”,当达到 365 天标记时,它会重置计数,并开始另一个循环。

data have;
    informat date ddmmyy10.;
    input date units_sold brand $ year;
    format date date9.;
    cards;
18/03/2010                5                   A         2010
12/04/2010                2                   A         2010
22/05/2010                1                   A         2010
25/05/2010                7                   A         2010
11/08/2011                5                   A         2011
12/07/2010                2                   B         2010
22/10/2010                1                   B         2010
05/05/2011                7                   B         2011
;

proc sort data=have;
    by brand date;
run;

data want;
    do until (last.brand);
        set have;
        by brand date;

        if first.brand then
            do;
                Sales_Over_365=0;
                _end=intnx('day',date,365);
            end;

        if date <= _end then
            Sales_Over_365+units_sold;
        else
            do;
                output;
                Sales_Over_365=units_sold;
                _end=intnx('day',date,365);
            end;
    end;

    output;
    drop _end;
run;

【讨论】:

  • 同意@Joe。有时你不假思索地编写代码,目前,我使用 intnx() 绝对比 +365 更容易。
  • @Joe 我不敢苟同。在这样的社区中,不仅要找到解决方案,而且要探索解决问题的不同方法。 Haikuo 的“day”让我进一步搜索了intnx 函数并探索了它的不同选项,因为在答案部分他是唯一一个使用“day”而不是“year”的人。
  • @Noob_Strider 当然,这将是我的下一个建议 - 使用 year。但是几天来这毫无意义,因为它们是单个单元...
【解决方案3】:

您需要为每个品牌指定开始日期。现在我们可以使用第一个销售日期,但这可能不是您想要的。然后,您可以将每个销售日期归类为该品牌的年份。

让我们从您的示例数据创建一个数据集开始。不需要 YEAR 变量。

data have ;
  input Date Units_Sold Brand $ Year ;
  informat date ddmmyy10.;
  format date yymmdd10.;
cards;
18/03/2010 5 A 2010
12/04/2010 2 A 2010
22/05/2010 1 A 2010
25/05/2010 7 A 2010
11/08/2011 5 A 2011
12/07/2010 2 B 2010
22/10/2010 1 B 2010
05/05/2011 7 B 2011
;;;;

现在我们可以通过 SQL 查询得到您想要的答案。

proc sql ;
  create table want as
   select brand
        , start_date
        , 1+floor((date - start_date)/365) as sales_year
        , intnx('year',start_date,calculated sales_year -1,'same')
            as start_sales_year format=yymmdd10.
        , sum(units_sold) as total_units_sold
  from
  ( select brand
        , min(date) as start_date format=yymmdd10.
        , date
        , units_sold
    from have
    group by 1
   )
  group by 1,2,3,4
  ;
quit;

这将产生这个结果:

                                               total_
                       sales_      start_      units_
Brand    start_date     year     sales_year     sold
  A      2010-03-18       1      2010-03-18      15
  A      2010-03-18       2      2011-03-18       5
  B      2010-07-12       1      2010-07-12      10

【讨论】:

    【解决方案4】:

    没有直接的方法可以做到这一点。你可以这样做。

    为了测试代码,我将您的表格保存到一个文本文件中。

    然后我创建了一个名为 Sale 的类。

    public class Sale
    {
        public DateTime Date { get; set; }
        public int UnitsSold { get; set; }
        public string Brand { get; set; }
        public int Year { get; set; }
    }
    

    然后我使用保存的文本文件填充了List&lt;Sale&gt;

    var lines = File.ReadAllLines(@"C:\Users\kosala\Documents\data.text");
    var validLines = lines.Where(l => !l.Contains("Date")).ToList();//remove the first line.
    
    List<Sale> sales = validLines.Select(l => new Sale()
            {
                Date = DateTime.Parse(l.Substring(0,10)),
                UnitsSold = int.Parse(l.Substring(26,5)),
                Brand = l.Substring(46,1),
                Year = int.Parse(l.Substring(56,4)),
            }).ToList();
    
    //All the above code is for testing purposes. The actual code starts from here.
    var totalUnitsSold = sales.OrderBy(s => s.Date).GroupBy(s => s.Brand);
    
            foreach (var soldUnit in totalUnitsSold)
            {
                DateTime? minDate = null;
                DateTime? maxDate = null;
                int total = 0;
                string brand = "";
    
                foreach (var sale in soldUnit)
                {
                    brand = sale.Brand;
                    if (minDate == null)
                    {
                        minDate = sale.Date;
                    }
                    if ((sale.Date - minDate).Value.Days <= 365)
                    {
                        maxDate = sale.Date;
                        total += sale.UnitsSold;
                    }
                    else
                    {
                        break;
                    }
                }
                Console.WriteLine("Brand : {0} UnitsSold Between {1} - {2} is {3}",brand, minDate.Value, maxDate.Value, total);
       }
    

    【讨论】:

    • 感谢您的回复。虽然我看到了您的代码背后的原因,但我无法对其进行测试,因为它的格式不同于 Base SAS。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    • 2017-06-06
    相关资源
    最近更新 更多