2017-11-29 9 views
0

하나의 문제점이 있으며 올바르게 작동하는 것이 많지 않다고 생각합니다. 내가 (원하는 출력 열 'sum_usage'로) 테이블이 :SAS에서 시간 간격으로 한 열에 대한 롤링 합계를 계산하십시오.

id opt t_purchase   t_spent  bonus usage sum_usage 
a 1 10NOV2017:12:02:00 10NOV2017:14:05:00 100  9  15 
a 1 10NOV2017:12:02:00 10NOV2017:15:07:33 100  0  15 
a 1 10NOV2017:12:02:00 10NOV2017:13:24:50 100  6  6 
b 1 10NOV2017:13:54:00 10NOV2017:14:02:58 100  3  10 
a 1 10NOV2017:12:02:00 10NOV2017:20:22:07 100 12  27 
b 1 10NOV2017:13:54:00 10NOV2017:13:57:12 100  7 .  7 

그래서, 내가 time_purchase에서 모든 사용 값을 합산 할 필요는 (하나 개의 아이디, 옵트 조합 (ID, 옵트에 의해 그룹)에 대한 하나가 고유 한 time_purchase)까지 t_spent. 또한 milion 행에 대해서도 알기 때문에 해시 테이블이 최상의 솔루션입니다.

data want; 
if _n_=1 then do; 
    if 0 then set have(rename=(usage=_usage)); 
    declare hash h(dataset:'have(rename=(usage=_usage))',hashexp:20); 
    h.definekey('id','opt', 't_purchase', 't_spent'); 
    h.definedata('_usage'); 
    h.definedone(); 
end; 
set have; 
sum_usage=0; 
do i=intck('second', t_purchase, t_spent) to t_spent ; 
if h.find(key:user,key:id_option,key:i)=0 then sum_usage+_usage; 
end; 
drop _usage i; 
run; 

바닥에서 다섯 번째 라인은 확실히 (do i=intck('second', t_purchase, t_spent)에 대한 올바른 아니지만,이 문제를 접근하는 방법을 아무 생각이 : 나는 함께 시도했습니다. 따라서, 주된 문제점은 이것을 계산하기위한 시간 간격을 설정하는 방법입니다. 나는 이미 같은 키를 가진이 해시 테이블 func에서 하나의 함수를 가지고있다. 그러나 시간 간격이 없기 때문에 이것을 쓰는 것이 꽤 좋을 것이다. 그러나 그것은 필요하지 않다.

+0

나는 포함 할 논리를 이해하는 데 어려움을 겪고 있습니다. 왜 총 사용량 21의 4 번째'id = a' 값입니까? 조금만 더 설명해주세요. – DomPazz

+0

'intck' 함수가 작동하면't_purchase'와't_spent' 사이의 초 수를 반환합니다. datetime 값은 01-01-1960 이후 초 단위로 저장되기 때문에't_spent-t_purchase'를 쓰는 것과 같습니다. 't_purchase = '01JAN2017 : 00 : 00 : 00'dt'이고't_spent ='01JAN2017 : 00 : 00 : 01'dt'라고 가정 해 봅시다. 본질적으로 1에서 1798848001로 반복하고 있습니다. 확실하지 않습니다. 이것이 당신이하고자하는 일입니다. – user2877959

+0

또한'find' 해시 테이블 호출이 나머지 코드와 일치하지 않는 것 같습니다. 이 문맥에 존재하지 않는 것처럼 보이는'user'와'id_option' 변수를 refferening하고 있습니다. – user2877959

답변

1

개인적으로 저는 해시를 버리고 SQL을 사용합니다.

예 데이터 :

data have; 

input id $ opt  
    t_purchase datetime20. 
    t_spent  datetime20. 
    bonus usage sum_usage; 

format 
    t_purchase datetime20. 
    t_spent  datetime20.; 

datalines; 
a 1 10NOV2017:12:02:00 10NOV2017:14:05:00 100  9  15 
a 1 10NOV2017:12:02:00 10NOV2017:15:07:33 100  0  15 
a 1 10NOV2017:12:02:00 10NOV2017:13:24:50 100  6  6 
b 1 10NOV2017:13:54:00 10NOV2017:14:02:58 100  3  10 
a 1 10NOV2017:12:02:00 10NOV2017:20:22:07 100 12  27 
b 1 10NOV2017:13:54:00 10NOV2017:13:57:12 100  7  7 
; 

내가 비교를 위해 귀하의 sum_usage 열을 떠날거야.

이제 합계 테이블을 만드십시오. 새 값은 sum_usage2입니다.

proc sql noprint; 
create table sums as 
select a.id, 
     a.opt, 
     a.t_purchase, 
     a.t_spent, 
     sum(b.usage) as sum_usage2 
    from have as a, 
     have as b 
    where a.id = b.id 
     and a.opt = b.opt 
     and b.t_spent <= a.t_spent 
     and b.t_spent >= a.t_purchase 
    group by a.id, 
     a.opt, 
     a.t_purchase, 
     a.t_spent; 
quit; 

이제이 금액을 가지고, 원래 테이블에 다시 가입 :

proc sql noprint; 
create table want as 
select a.*, 
     b.sum_usage2 
    from have as a 
     left join 
     sums as b 
     on a.id = b.id 
     and a.opt = b.opt 
     and a.t_spent = b.t_spent 
     and a.t_purchase = b.t_purchase; 
quit; 

이 원하는 테이블을 생성합니다. 또는 해시를 사용하여 값을 조회하고 데이터 단계에서 합계를 더할 수 있습니다 (크기가 주어질 때 더 빠름). 나는이 질문을 생각 https://github.com/FinancialRiskGroup/SASPerformanceAnalytics

1

여기에 해당

data want2; 
set have; 
format sum_usage2 best.; 
if _n_=1 then do; 
    %create_hash(lk,id opt t_purchase t_spent, sum_usage2,"sums"); 
end; 

rc = lk.find(); 

drop rc; 
run; 

%create_hash() 매크로 초 각 레코드에 대해 3 시간 동안 모든 해시 조회를 수행하여 롤링 합계를 계산 한 당신의 이전 사람의 모프입니다 귀하의 데이터 세트에. 바라건대 당신은 그 접근 방식의 단순함이 레코드 당 3 * 3600 해시 룩업과 커다란 데이터 벡터를 해쉬에로드하는 데 드는 비용이 크다는 것을 알았기를 바랍니다.

제시된 시간 로그 데이터에는 데이터의 맨 위에 삽입 된 새로운 레코드가 있으며 시간이지나면서 단조로운 데이터로 추정됩니다.

데이터 스텝은 단조로운 데이터의 단일 패스에서 시간 창에 대한 롤링 합계를 계산할 수 있습니다. 이 기법은 '반지'배열을 사용하며, 인덱스 진입은 모듈로 조정됩니다. 하나의 배열은 시간에 대한 것이고 다른 하나는 메트릭 (용도)에 대한 것입니다. 필요한 배열 크기는 시간 창 내에서 발생할 수있는 최대 항목 수입니다.

1, 2 시간 간격으로 일부 발생하는 샘플 데이터가 있다면, 200 초 하나 개 점프 :

data have; 
    time = '12oct2017:11:22:32'dt; 
    usage = 0; 
    do _n_ = 1 to &have_count; 
    time + 2; *ceil(25*ranuni(123)); 
    if _n_ > 30 then time + -1; 
    if _n_ = 145 then time + 200; 
    usage = floor(180*ranuni(123)); 
    delta = time-lag(time); 
    output; 
    end; 
run; 

시작 시간 오름차순 정렬 전 항목에서 롤링 합을 계산하는 경우에.(내림차순의 경우) : 16 및 TIME_WINDOW은 12 초입니다.

%let RING_SIZE = 16; 
%let TIME_WINDOW = '00:00:12't; 

data want; 
    array ring_usage [0:%eval(&RING_SIZE-1)] _temporary_ (&RING_SIZE*0); 
    array ring_time [0:%eval(&RING_SIZE-1)] _temporary_ (&RING_SIZE*0); 

    retain ring_tail 0 ring_head -1 span 0 span_usage 0; 

    set have; 
    by time ; * cause error if data not sorted per algorithm requirement; 

    * unload from accumulated usage the tail items that fell out the window; 
    do while (span and time - ring_time(ring_tail) > &TIME_WINDOW); 
    span + -1; 

    span_usage + -ring_usage(ring_tail); 
    ring_tail = mod (ring_tail + 1, &RING_SIZE) ; 
    end; 

    ring_head = mod (ring_head + 1, &RING_SIZE); 
    span + 1; 

    if span > 1 and (ring_head = ring_tail) then do; 
    _n_ = dim(ring_time); 
    put 'ERROR: Ring array too small, size=' _n_; 
    abort cancel; 
    end; 

    * update the ring array; 
    ring_time(ring_head) = time; 
    ring_usage(ring_head) = usage; 

    span_usage + usage; 

    drop ring_tail ring_head span; 
run; 

데이터가 내림차순으로 정렬 된 경우 물건을 흔들 수 있습니다. 오름차순 정렬, 롤링 및 리조트 내림차순 계산

그런 지글 거리는 짓을 할 수 없거나 한 번만 할까?

롤링 계산에 포함될 항목은 '리드'행 또는 SET을 통해 아직 읽지 않은 행이어야합니다. 이것이 어떻게 가능한지 ? 두 번째 SET 문을 사용하여 데이터 세트에 대한 별도의 채널을 열고 따라서 리드 값을 얻을 수 있습니다.

리드 데이터를 처리하는 데 더 많은 부기가 있습니다. 데이터 끝 부분의 조기 덮어 쓰기 및 감소 된 창을 처리해야합니다. I는 SQL 대 링 어레이 벤치마킹하지

proc sort data=want2; by time; 
run; 

proc compare noprint data=want compare=want2 out=diff outnoequal; 
    id time; 
    var span_usage; 
run; 
---------- LOG ---------- 
NOTE: There were 150 observations read from the data set WORK.WANT. 
NOTE: There were 150 observations read from the data set WORK.WANT2. 
NOTE: The data set WORK.DIFF has 0 observations and 4 variables. 

해시 대 EXPAND PROC 대 :

data want2; 
    array ring_usage [-1:%eval(&RING_SIZE-1)] _temporary_; 
    array ring_time [-1:%eval(&RING_SIZE-1)] _temporary_; 

    retain lead_index 0 ring_tail -1 ring_head -1 span 1 span_usage . guard_index .; 

    set have; 

&debug put/_N_ ':' time= ring_head=; 

    * unload ring_head slotted item from sum; 
    span + -1; 
    span_usage + -ring_usage(ring_head); 

    * advance ring_head slot by 1, the vacated slot will be overwritten by lead; 
    ring_head = mod (ring_head + 1, &RING_SIZE); 

&debug put +2 ring_time(ring_head)= span= 'head'; 

    * load ring with lead values via a second SET of the same data; 
    if not end2 then do; 

    do until (_n_ > 1 or lead_index = 0 or end2); 
     set have(keep=time usage rename=(time=t usage=u)) end=end2; * <--- the second SET ; 

     if end2 then guard_index = lead_index; 

&debug if end2 then put guard_index=; 

     ring_time(lead_index) = t; 
     ring_usage(lead_index) = u; 

&debug put +2 ring_time(lead_index)= 'lead'; 

     lead_index = mod (lead_index + 1, &RING_SIZE); 
    end; 
    end; 

    * advance ring_tail to cover the time window; 
    if ring_tail ne guard_index then do; 

     ring_tail_was = ring_tail; 
     ring_tail = mod (ring_tail + 1, &RING_SIZE) ; 

     do while (time - ring_time(ring_tail) <= &TIME_WINDOW); 

      span + 1; 
      span_usage + ring_usage(ring_tail); 

&debug put +2 ring_time(ring_tail)= span= 'seek'; 

      ring_tail_was = ring_tail; 
      ring_tail = mod (ring_tail + 1, &RING_SIZE) ; 

      if ring_tail_was = guard_index then leave; 

      if span > 1 and (ring_head = ring_tail) then do; 
      _n_ = dim(ring_time); 
      put 'ERROR: Ring array too small, size=' _n_; 
      abort cancel; 
      end; 
     end; 

     * seek went beyond window, back tail off to prior index; 
     ring_tail = ring_tail_was; 

    end; 

&debug put +2 ring_time(ring_tail)= span= 'mark'; 

    drop lead_index t u ring_: guard_index span; 

    format ring: span: usage 6.; 
run; 
options source; 

확인 두 방법 모두 동일한 연산이있다.

주의 : + in 및 -out 연산을 사용하여 롤링 값을 추측 할 때 정수가 아닌 값을 처리 할 때 반올림 오류가 발생할 수 있습니다.