SAS - formula for creation of one column from another - function

My problem is following - I am creating a column (diskont_faktor) from another one (disc_pc_nonann) with the formula for creation differing based on the row number.
for row #1 the function is diskont_faktor = 1/disc_pc_nonann;
for row #2 to n the function is diskont_faktor = diskont_faktor(t-1)/disc_pc_nonann;
I tried following code:
data soubor2;
set srv.data;
disc_pc_nonann = (1+DISC_PC/100)**(1/12);
if _n_ = 1 then diskont_faktor = 1/disc_pc_nonann;
else diskont_faktor = lag1(diskont_faktor)/disc_pc_nonann;
run;
But SAS does not calculate values for rows > 1.
Is there some specific reason why function lag does not work in this setting of code ?
I tried even this version without success:
data soubor2;
set srv.data;
disc_pc_nonann = (1+DISC_PC/100)**(1/12);
if _n_ = 1 then diskont_faktor = 1/disc_pc_nonann;
else do; y=lag1(diskont_faktor);
diskont_faktor = y/disc_pc_nonann;
end;
run;
Thank you for any advice what I am doing wrong.

The LAG() function does not return the value from the previous observation. Instead it returns the previous value from the stack that it generates as it it is called. So by only executing the lag() for some of the observations you are not properly stacking the values.
One easy way to work around this is to place the value of the LAG() function call into a variable. That way it runs for every observation so it always returns the previous value. You can then conditionally reference the variable if you want without interrupting the stream of values for the LAG() function.
lag_diskont_faktor=lag(diskont_faktor);
if _n_ = 1 then diskont_faktor = 1/disc_pc_nonann;
else diskont_faktor = lag_diskont_faktor/disc_pc_nonann;
But for your problem it looks like you do not really need to use LAG(). You can just use RETAIN to prevent SAS from setting the new variable to missing when it starts the next iteration of the DATA step.
retain diskont_faktor;
if _n_ = 1 then diskont_faktor = 1/disc_pc_nonann;
else diskont_faktor = diskont_faktor/disc_pc_nonann;
You could even set the initial value for the variable in the RETAIN statement and simplify the code.
retain diskont_faktor 1;
diskont_faktor = diskont_faktor/disc_pc_nonann;

You need to initialize your variable diskont_faktor at the start of your program, so add an retain diskont_faktor; at the start.
The first call of lag1 returns you a . , because the queue is empty at that time, so for _n_ = 2 it would return nothing but store your value from _n_= 1 in the queue. _n_ = 3 should work but store your empty result from _n_=2 in the queue. Because for n=2 you had no result, _n_= 4 will return nothing again. This goes on and on, so that you have results for odd _n_ and no result for even _n_.
So i dont know how to solve this whith lag,
but I can provide you an alternative solution without lag:
data soubor2 (drop=diskont_faktor_old);
retain diskont_faktor_old;
set srv.data;
disc_pc_nonann = (1+DISC_PC/100)**(1/12);
if _n_ = 1 then diskont_faktor = 1/disc_pc_nonann;
else diskont_faktor = diskont_faktor_old/disc_pc_nonann;
diskont_faktor_old=diskont_faktor;
run;

Related

Update planned order - two committed modifications, only one saved

I need to update two information on one object: the quantity (PLAF-gsmng) and refresh the planned order via the module function 'MD_SET_ACTION_PLAF'.
I successfully find a way to update each data separately. But when I execute the both solutions the second modification is not saved on the database.
Do you know how I can change the quantity & set the action on PLAF (Planned order) table ?
Do you know other module function to update only the quantity ?
Maybe a parameter missing ?
It's like if the second object is locked (sm12 empty, no sy-subrc = locked) ... and the modification is not committed.
I tried to:
change the order of the algorithm (refresh and after, change PLAF)
add, remove, move the COMMIT WORK & COMMIT WORK AND WAIT
add DEQUEUE_ALL or DEQUEUE_EMPLAFE
This is the current code:
1) Read the data
lv_plannedorder = '00000000001'
"Read PLAF data
SELECT SINGLE * FROM PLAF INTO ls_plaf WHERE plnum = lv_plannedorder.
2) Update Quantity data
" Standard configuration for FM MD_PLANNED_ORDER_CHANGE
CLEAR ls_610.
ls_610-nodia = 'X'. " No dialog display
ls_610-bapco = space. " BAPI type. Do not use mode 2 -> Action PLAF-MDACC will be autmatically set up to APCH by the FM
ls_610-bapix = 'X'. " Run BAPI
ls_610-unlox = 'X'. " Update PLAF
" Customize values
MOVE p_gsmng TO ls_plaf-gsmng. " Change quantity value
MOVE sy-datlo TO ls_plaf-mdacd. " Change by/datetime, because ls_610-bapco <> 2.
MOVE sy-uzeit TO ls_plaf-mdact.
CALL FUNCTION 'MD_PLANNED_ORDER_CHANGE'
EXPORTING
ecm61o = ls_610
eplaf = ls_plaf
EXCEPTIONS
locked = 1
locking_error = 2
OTHERS = 3.
" Already committed on the module function
" sy-subrc = 0
If I go on the PLAF table, I can see that the quantity is edited. It's working :)
3) Refresh BOM & change Action (MDACC) and others fields
CLEAR ls_imdcd.
ls_imdcd-pafxl = 'X'.
CALL FUNCTION 'MD_SET_ACTION_PLAF'
EXPORTING
iplnum = lv_plannedorder
iaccto = 'BOME'
iaenkz = 'X'
imdcd = ls_imdcd
EXCEPTIONS
illegal_interface = 1
system_failure = 2
error_message = 3
OTHERS = 4.
IF sy-subrc = 0.
COMMIT WORK.
ENDIF.
If I go on the table, no modification (only the modif. of the part 2. can be found on it).
Any idea ?
Maybe because the ls_610-bapco = space ?
It should be possible to update planned order quantity with MD_SET_ACTION_PLAF too, at least SAP Help tells us so. Why don't you use it like that?
Its call for changing the quantity should possibly look like this:
DATA: lt_acct LIKE TABLE OF MDACCTO,
ls_acct LIKE LINE OF lt_acct.
ls_acct-accto = 'BOME'.
APPEND lt_acct.
ls_acct-accto = 'CPOD'.
APPEND lt_acct.
is_mdcd-GSMNG = 'value' "updated quantity value
CALL FUNCTION 'MD_SET_ACTION_PLAF'
EXPORTING
iplnum = iv_plnum
iaenkz = 'X'
IVBKZ = 'X'
imdcd = is_mdcd "filled with your BOME-related data + new quantity
TABLES
TMDACCTO = lt_accto
EXCEPTIONS
illegal_interface = 1
system_failure = 2
error_message = 3.
So there is no more need for separate call of MD_PLANNED_ORDER_CHANGE anymore and no more problems with update.
I used word possibly because I didn't find any example of this FM call in the Web (and SAP docu is quite ambiguous), so I propose this solution just as is, without verification.
P.S. Possible actions are listed in T46AS table, and possible impact of imdcd fields on order can be checked in MDAC transaction. It is somewhat GUI equivalent of this FM for single order.

Only first pass trough logic gates raising a flag, second deactivating it, others doing nothing

I would like to model a bitarray of flags, where only the first pass through a logical expression sets a flag, but the second pass (computing with the previous state) deactivates the flag and all other passes do nothing to it.
I cannot think of a right combo of the previous state, the right logical expression and a second input (0/1).
bitarray[i] = operand(bitarray[i], value_making_a_change)
Desired value after the 1. pass: bitarray[i] = boolean_value.
Desired value after the second pass: bitarrayp[i] = neg(boolean_value).
Desired value after every consequential pass: bitarrayp[i] = neg(boolean_value).
I think I could just test the value, but my bitarray could get quite large, so I would not want to do that for every index. Is there a standard logic gate/expression I could use?
I tried just using XOR(old_value, 1), but that changes the value back and forth:
value = 0
value = XOR(value, 1) = XOR(0, 1) = 1
value = XOR(value, 1) = XOR(1, 1) = 0
value = XOR(value, 1) = XOR(0, 1) = 1
...

Using .Find in a Recursive Function

I am trying to find the row number in a sheet using the .Find function in a recursive function.
I set an object called Found = .Find.... and it works great... for a little bit. I set it when I'm 1 level of recursion deep, then set it again when I'm 2 levels deep. Then, my code finds the end of the path and starts backing up until it gets back to 1 level deep, but not my Found object has been re-declared and kept its values from the 2nd level. My other variables (ThisRow etc...) keep the value of the level that they are in, and that's what I would like to do with the object Found. Is there a way that I can declare Found locally so that it's value doesn't extend to the next function, and can't be overwritten in a deeper level? You can find my code below for reference.
Here is my current code - irrelevant parts cut out:
Public Function FindChildren()
ThisRow = AnswerRow 'Also declared before function call
BeenHereCell = Cells(ThisRow, "O").Address
If Range(BeenHereCell).Value = "Yes" Then
Exit Function 'That means we've already been there
End If
Range(BeenHereCell).Value = "Yes"
With Worksheets("MasterScore").Range("j1:j50000")
Set Found = .Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
firstAddress = Found.Address
NextCell = Found.Address
Do
AnswerRow = Range(NextCell).Row
FindChildren 'This is where it's recursive.
Set Found = .FindNext(Found)
NextCell = Found.Address
Loop While Not Found Is Nothing And Found.Address <> firstAddress
End If
End With
End Function
Now I have gotten around it by activating cells, but it makes my code a lot slower. Currently I am using this:
Set Found = Worksheets("MasterScore").Range("j1:j50000").Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
Count = 1
Do
Columns("J:J").Select
FirstFoundRow = Selection.Find(What:=NextQuestionID, After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False).Row
For i = 1 To Count
Selection.FindNext(After:=ActiveCell).Activate
Next i
AnswerRow = ActiveCell.Row
If AnswerRow = FirstFoundRow And Count <> 1 Then Exit Do
FindChildren
Count = Count + 1
Loop
End If
This way, I don't have to set the value of the object again, but I have to iterate through it.FindNext quite a few times and each time it runs that line its also activating the row. I really just want something like.
AnswerRow = .Find(nth instance of NextQuestionID).Row
(I have about 50k rows and the count goes to about 20 pretty often so it really takes a while).
I'd appreciate any ideas! Currently my code is working, but it's going to take a good part of the day to complete, and I'll need to run this again at some point!
I ended up finding a way to speed it up a little bit. I think this could help someone so I will share what I've found.
It's not the best solution (I would have preferred to just declare the object locally so my other functions wouldn't change it's value), but at least with this I'm not looping through 20 or so finds every iteration of the Do Loop.
Set Found = Worksheets("MasterScore").Range("j1:j50000").Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
NextAnswerRange = "j" & 1 & ":" & "j50000" 'The first search will be from the beginning
Do
Set Found = Worksheets("MasterScore").Range(NextAnswerRange).Find(NextQuestionID, LookIn:=xlValues)
NextCell = Found.Address
AnswerRow = Range(NextCell).Row
NextAnswerRange = "j" & AnswerRow & ":" & "j50000"
If LastAnswerRange = NextAnswerRange Then Exit Function 'This would mean we've reached the end.
LastAnswerRange = NextAnswerRange
FindChildren
Loop
End If
End Function
So we know we've already covered our bases with previous ranges since it always finds the immediate next. We just change the range of the search each time and it will find the next value.
A weird thing about this solution is that if you are looking for a value among range 70 -> 50,000 and you have the answer your looking for on row 70, it will actually find the next row (it skips that first one). But, if there aren't any rows past 70 that have the answer, it will actually take the value from row 70. That meant that I couldn't do
NextAnswerRange = "j" & AnswerRow + 1 & ":" & "j50000"
because it would miss some values. Doing it without the + 1 meant at the end of the document I would end up searching the same last value over and over (it would never go back to Found Is Nothing) so I had to put in the check to see if the LastAnswerRange = NextAnswerRange.
I hope this helps someone. I don't think it's the most elegant solution, but it's a lot faster than what I had.

Tips for function inside while loop and i=i+1, Matlab

I have a problem with a function in matlab. This specific function is for filtering light signals. As you can see below I added the coding I’ve used in the function and in the while loop itself. The code is written for a NXT Lego robot.
Is there any tip how to get the count variable ( i = i + 1 ) to work in the function, so we can plot Light(i)? Because we’re getting a bunch of error messages when we try different codes to make it work.
function [light] = filter_func( i)
lightI(i) = GetLight(SENSOR_3);
if i==1
light(i)=lightI(i)
elseif i==2
light(i) = 0.55*lightI(i) + 0.45*lightI(i-1)
else
light(i) = 0.4*lightI(i) + 0.3*lightI(i-1) + 0.3*lightI(i-2);
end
end
i=1
while true
lightI(i) = GetLight(SENSOR_3); % Get’s a lightvalue between 0 and 1024.
if i>2
light =filter_func(i)
light=round(light);
else
light(i) = GetLight(SENSOR_3);;
end
i=1+i
plot(light(end-90:end), 'r-');
title('Lightvalue')
axis([0 100 0 1023]) ;
end
You probably mainly get errors because you are not allowed to mix script and functions like this in MATLAB (like you are in Python).
Your filter function is only used when i>2 so why are you doing the first 2 tests? It seems like you want lightI as a global variable, but that is not what you have done. The lightI inside the function is not the same as the one in the while loop.
Since your while loop runs forever, maybe you don't need to worry about updating the plot the first two times. In that case you can do this:
filter = [0.4 0.3 0.3]';
latest_filtered_light = nan(90,1);
lightI = [];
p = plot(latest_filtered_light, 'r-');
title('Lightvalue')
axis([0 100 0 1023]) ;
while True
lightI(end+1,1) = rand*1024; % Get’s a lightvalue between 0 and 1024.
if i>=3
new_val = lightI(end-2:end,1)'*filter;
latest_filtered_light = [latest_filtered_light(2:end);...
new_val];
set(p, 'ydata', latest_filtered_light)
drawnow
end
end
I think it is an important point to not call plot every time - at least if you are the least concerned about performance.

Calling function with changing input parameters in a loop Matlab

I have caught myself in a issue, I know its not that difficult but I couldnt figure out how to implement it. I have an m file that looks like
clear;
PVinv.m_SwF=20e3
for m=1:1:70;
PVinv.m_SwF=PVinv.m_SwF+1e3;
Lmin = PVinv.InductanceDimens();
Wa_Ac = PVinv.CoreSizeModel();
PVinv.CoreSelect(Wa_Ac);
[loss_ind_core,loss_ind_copper] = PVinv.InductorLossModel(PVinv.m_L_Selected);
Total_Inductor_Loss=loss_ind_core+loss_ind_copper
plot(PVinv.m_SwF,Total_Inductor_Loss,'--gs');
hold on
xlim([10e3 90e3])
set(gca,'XTickLabel',{'10';'20';'30';'40';'50';'60';'70';'80';'90'})
grid on
xlabel('Switching Frequency [kHz]');
ylabel('Power loss [W]');
end
And the function that is of interest is CoreSelect(Wa_Ac)
function obj = CoreSelect(obj, WaAc)
obj.m_Core_Available= obj.m_Core_List(i);
obj.m_L_Selected.m_Core = obj.m_Core_Available;
end
I want to change the value of i from obj.m_Core_List(1) to obj.m_Core_List(27) within that for loop of main m file. How can I get the value of the function coreselect when I call it in main m file
For eg for m=1 to 70 I want the function to take the value of i=1 then execute till plot command and then same with but i=2 and so on
Any suggestion would be really helpful
I'm not sure I understand your question perfectly, but I think you want to pass an index i to the CoreSelect function, and loop i from 1 to 27 outside of the function. Try this:
function obj = CoreSelect(obj, WaAc, i)
...
end
for i=1:27,
PVInv.CoreSelect(WaAc,i);
end