Агрегирование за фактический год в SAS

Предположим, у нас есть следующая таблица («Покупки»):

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 года для разных брендов.

Что я хочу сделать, так это подсчитать количество проданных единиц для каждого бренда в каждом году. Однако я хочу делать это не для календарного года, а для фактического года.

Итак, пример того, что я не хочу:

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

Приведенная выше логика в порядке, если мы знаем, что, например. Бренд «А» существует на протяжении всего 2010 года. Но если Бренд «А» впервые появился 18.03.2010 и существует до сих пор, то сравнение 2010 и 2011 годов будет не совсем корректным, так как для 2010 года мы "не хватает" 3 месяцев.

Итак, я хочу рассчитать:

для А: сумма с 18.03.2010 по 17.03.2011, затем с 18.03.2011 по 17.03.2012 и т.д.

для Б: сумма с 07.12.2010 по 07.11.2011 и т.д.

и так далее для всех брендов.

Есть ли умный способ сделать это?


person Noob_Strider    schedule 05.01.2016    source источник
comment
Ваш вопрос не ясен. В чем разница между методом, который вам не нужен, и методом, который вам нужен?   -  person Kosala W    schedule 05.01.2016
comment
В методе не хочу результаты, которые я получу, — это общее количество проданных единиц за календарные годы (например, 2010). В методе хочу я хотел бы рассчитать общее количество проданных единиц за полный год бренда с момента его первого появления в наборе данных. Полный год для бренда не ограничивается периодом с 01.01.2010 по 31.12.2010, а может быть с 18.03.2010 по 17.03.2011. Имеет ли это смысл?   -  person Noob_Strider    schedule 05.01.2016
comment
Так что же представляет собой год в этом случае? 365 дней, начиная с первой даты появления?   -  person Kosala W    schedule 05.01.2016
comment
А високосные годы?   -  person Joe    schedule 05.01.2016
comment
@ Джо, хорошо, что я не думал об этом (хотя 2016 год високосный!). Однако для нужд моего исследования я сомневаюсь, что один день что-то изменит. Но в академической среде ваше дополнение действует на 100%.   -  person Noob_Strider    schedule 06.01.2016


Ответы (4)


Шаг 1. Убедитесь, что ваш набор данных отсортирован или проиндексирован по бренду и дате

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

Шаг 2. Рассчитайте даты начала и окончания для каждого продукта

Идея кода ниже:

  1. Мы знаем, что первое появление бренда в отсортированном наборе данных — это день, когда бренд был представлен. Мы назовем это Product_Year_Start.

  2. Функцию intnx можно использовать для увеличения этой даты на 365 дней, а затем вычесть из нее 1. Назовем эту дату Product_Year_End.

  3. Поскольку теперь мы знаем дату окончания года продукта, мы знаем, что если дата в любой строке превышает дату окончания года продукта, мы начали следующий год продукта. Мы просто возьмем рассчитанные Product_Year_End и Product_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;
person Stu Sztukowski    schedule 05.01.2016
comment
Спасибо, что нашли время, чтобы организовать свой ответ, он был наверняка полезен и прост для понимания для тех, кто не сталкивался с некоторыми функциями в вашем коде, например. интнкс. - person Noob_Strider; 06.01.2016

Следующий код делает то, что вы просите, в буквальном смысле: для самой ранней «даты» каждого «бренда» он начинает агрегировать «единицы продажи», когда достигает отметки 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;
person Haikuo Bian    schedule 05.01.2016
comment
Согласен @Джо. Иногда вы кодируете, не задумываясь, на данный момент мне определенно проще сделать intnx(), чем просто +365. - person Haikuo Bian; 05.01.2016
comment
@ Джо, я позволю себе не согласиться. В таком сообществе важно не только найти решение, но и изучить различные способы решения проблемы. День Хайкуо заставил меня продолжить поиск функции intnx и изучить ее различные варианты, поскольку в разделе ответов он единственный, кто использовал день вместо года. - person Noob_Strider; 06.01.2016
comment
@Noob_Strider Конечно, и это должно было быть моим следующим предложением - используйте year. Но в течение нескольких дней это довольно бессмысленно, поскольку они являются отдельными единицами ... - person Joe; 06.01.2016

У вас должна быть дата начала для каждого бренда. Пока мы можем использовать первую дату продажи, но это может быть не то, что вам нужно. Затем вы можете классифицировать каждую дату продажи, по какому году она относится к этому бренду.

Давайте начнем с создания набора данных из ваших образцов данных. Переменная 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
person Tom    schedule 05.01.2016

Нет прямого способа сделать это. Вы можете сделать что-то вроде этого.

Чтобы проверить код, я сохранил вашу таблицу в текстовом файле.

Затем я создал класс под названием 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<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);
   }
person Kosala W    schedule 05.01.2016
comment
Спасибо за ваш ответ. Хотя я понимаю причину вашего кода, я не могу его протестировать, так как он имеет формат, отличный от Base SAS. - person Noob_Strider; 05.01.2016