在 SAS 中汇总实际年份

Aggregating Over Actual Year in SAS

假设我们有以下 table ("Purchases"):

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;

如果我们知道例如Brand "A" 存在于整个 2010 年。但是如果 Brand "A" 第一次出现在 18/03/2010,并且一直存在到现在,那么将 2010 年和 2011 年进行比较就不够好了至于 2010 年,我们 "lacking" 3 个月。

所以我想做的是计算:

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

对于 B:从 12/07/2010 到 11/07/2011 等的总和

所有品牌依此类推。

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

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

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

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

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

然后我使用保存的文本文件填充了 List<Sale>

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);
   }

下面的代码是按字面意思做的,对于每个 'brand' 中最早的 'date',它开始聚合 'unitssold',当达到 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;

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

让我们从您的示例数据创建数据集开始。不需要 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

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

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

第 2 步:计算每个产品的 start/end 日期

下面代码背后的想法:

  1. 我们知道,品牌在排序后的数据集中首次出现的时间是该品牌推出的那一天。我们称之为 Product_Year_Start

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

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

这一切都是使用 by-group 处理和 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 代码,按新产品 start/end 日期

分组
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;