在 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 日期
下面代码背后的想法:
我们知道,品牌在排序后的数据集中首次出现的时间是该品牌推出的那一天。我们称之为 Product_Year_Start
。
intnx
函数可用于将该日期递增 365 天,然后从中减去 1。我们称这个日期为 Product_Year_End
.
由于我们现在知道产品的年终日期,我们知道如果任何给定行上的日期超过产品的年终日期,我们就会开始下一个产品年。我们将只计算该品牌的 Product_Year_End
和 Product_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;
假设我们有以下 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 日期
下面代码背后的想法:
我们知道,品牌在排序后的数据集中首次出现的时间是该品牌推出的那一天。我们称之为
Product_Year_Start
。intnx
函数可用于将该日期递增 365 天,然后从中减去 1。我们称这个日期为Product_Year_End
.由于我们现在知道产品的年终日期,我们知道如果任何给定行上的日期超过产品的年终日期,我们就会开始下一个产品年。我们将只计算该品牌的
Product_Year_End
和Product_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;