3
3
4
4
import re
5
5
from pathlib import Path
6
- from typing import Union , Optional , Callable , List , Tuple , Any , Generator
6
+ from typing import Union , Optional , Callable , List , Tuple , Any , Generator , Type
7
7
from warnings import warn
8
8
9
9
import pandas as pd
15
15
MAS_CODE_NAME = "dmcas_packagescorecode.sas"
16
16
CAS_CODE_NAME = "dmcas_epscorecode.sas"
17
17
18
- # TODO: add converter for any type of dataset (list, dataframe, numpy array)
19
-
20
18
21
19
class ScoreCode :
22
20
score_code : str = ""
@@ -161,7 +159,7 @@ def write_score_code(
161
159
else :
162
160
raise ValueError (
163
161
"Either the binary_string or the model_file_name argument needs to be"
164
- "specified in order to generate the score code."
162
+ " specified in order to generate the score code."
165
163
)
166
164
167
165
# Add the core imports to the score code with the specified model serializer
@@ -176,16 +174,16 @@ def write_score_code(
176
174
if model_id and not binary_string :
177
175
model_load = cls ._viya35_model_load (
178
176
model_id ,
179
- pickle_type ,
180
177
model_file_name ,
178
+ pickle_type = pickle_type ,
181
179
mojo_model = "mojo_model" in kwargs ,
182
180
binary_h2o_model = "binary_h2o_model" in kwargs ,
183
181
)
184
182
# As above, but for SAS Viya 4 models
185
183
elif not binary_string :
186
184
model_load = cls ._viya4_model_load (
187
- pickle_type ,
188
185
model_file_name ,
186
+ pickle_type = pickle_type ,
189
187
mojo_model = "mojo_model" in kwargs ,
190
188
binary_h2o_model = "binary_h2o_model" in kwargs ,
191
189
)
@@ -195,7 +193,7 @@ def write_score_code(
195
193
model_prefix = cls ._check_valid_model_prefix (model_prefix )
196
194
197
195
# Define the score function using the variables found in input_data
198
- cls .score_code += f"def score { model_prefix } ({ ', ' .join (input_var_list )} ):\n "
196
+ cls .score_code += f"def score_ { model_prefix } ({ ', ' .join (input_var_list )} ):\n "
199
197
200
198
if not score_metrics :
201
199
score_metrics = cls ._determine_score_metrics (
@@ -234,9 +232,10 @@ def write_score_code(
234
232
)
235
233
# Include check for numpy values and a conversion operation as needed
236
234
cls .score_code += (
237
- f"{ '' :4} prediction = [val.item() if type(val)."
238
- f"__module__ == np.__name__ else val for val in "
239
- f"prediction]\n "
235
+ f"\n { '' :4} # Check for numpy values and convert to a CAS readable "
236
+ f"representation\n "
237
+ f"{ '' :4} if isinstance(prediction, np.ndarray):\n "
238
+ f"{ '' :8} prediction = prediction.tolist()[0]\n \n "
240
239
)
241
240
cls ._predictions_to_metrics (
242
241
score_metrics ,
@@ -252,7 +251,7 @@ def write_score_code(
252
251
)
253
252
254
253
if score_code_path :
255
- py_code_path = Path (score_code_path ) / ( model_prefix + "_score .py")
254
+ py_code_path = Path (score_code_path ) / f"score_ { model_prefix } .py"
256
255
with open (py_code_path , "w" ) as py_file :
257
256
py_file .write (cls .score_code )
258
257
if model_id and score_cas :
@@ -263,7 +262,7 @@ def write_score_code(
263
262
# noinspection PyUnboundLocalVariable
264
263
sas_file .write (cas_code )
265
264
else :
266
- output_dict = {model_prefix + "_score .py" : cls .score_code }
265
+ output_dict = {f"score_ { model_prefix } .py" : cls .score_code }
267
266
if model_id and score_cas :
268
267
# noinspection PyUnboundLocalVariable
269
268
output_dict [MAS_CODE_NAME ] = mas_code
@@ -1000,19 +999,19 @@ def _binary_target(
1000
999
f"{ '' :8} { metrics } = \" { target_values [0 ]} \" \n "
1001
1000
f"{ '' :4} else:\n "
1002
1001
f"{ '' :8} { metrics } = \" { target_values [1 ]} \" \n \n "
1003
- f"return { metrics } "
1002
+ f"{ '' :4 } return { metrics } "
1004
1003
)
1005
1004
# One return that is the classification
1006
1005
elif len (returns ) == 1 and returns [0 ]:
1007
- cls .score_code += f"\n \n return prediction"
1006
+ cls .score_code += f"{ '' :4 } return prediction"
1008
1007
# One return that is a probability
1009
1008
elif len (returns ) == 1 and not returns [0 ]:
1010
1009
cls .score_code += (
1011
1010
f"{ '' :4} if prediction > { threshold } :\n "
1012
1011
f"{ '' :8} { metrics } = \" { target_values [0 ]} \" \n "
1013
1012
f"{ '' :4} else:\n "
1014
1013
f"{ '' :8} { metrics } = \" { target_values [1 ]} \" \n \n "
1015
- f"return { metrics } "
1014
+ f"{ '' :4 } return { metrics } "
1016
1015
)
1017
1016
# Two returns from the prediction method
1018
1017
elif len (returns ) == 2 and sum (returns ) == 0 :
@@ -1022,7 +1021,7 @@ def _binary_target(
1022
1021
f"{ '' :8} { metrics } = \" { target_values [0 ]} \" \n "
1023
1022
f"{ '' :4} else:\n "
1024
1023
f"{ '' :8} { metrics } = \" { target_values [1 ]} \" \n \n "
1025
- f"return { metrics } "
1024
+ f"{ '' :4 } return { metrics } "
1026
1025
)
1027
1026
# Classification and probability returned; return classification value
1028
1027
elif len (returns ) > 1 and sum (returns ) == 1 :
@@ -1041,7 +1040,7 @@ def _binary_target(
1041
1040
"score code should output the classification and probability for "
1042
1041
"the target event to occur."
1043
1042
)
1044
- cls .score_code += f"\n \n return prediction[1][0], prediction[1][2]"
1043
+ cls .score_code += f"{ '' :4 } return prediction[1][0], prediction[1][2]"
1045
1044
# Calculate the classification; return the classification and probability
1046
1045
elif sum (returns ) == 0 and len (returns ) == 1 :
1047
1046
warn (
@@ -1054,7 +1053,7 @@ def _binary_target(
1054
1053
f"{ '' :8} { metrics [0 ]} = \" { target_values [0 ]} \" \n "
1055
1054
f"{ '' :4} else:\n "
1056
1055
f"{ '' :8} { metrics [0 ]} = \" { target_values [1 ]} \" \n \n "
1057
- f"return { metrics [0 ]} , prediction"
1056
+ f"{ '' :4 } return { metrics [0 ]} , prediction"
1058
1057
)
1059
1058
# Calculate the classification; return the classification and probability
1060
1059
elif sum (returns ) == 0 and len (returns ) == 2 :
@@ -1068,11 +1067,11 @@ def _binary_target(
1068
1067
f"{ '' :8} { metrics [0 ]} = \" { target_values [0 ]} \" \n "
1069
1068
f"{ '' :4} else:\n "
1070
1069
f"{ '' :8} { metrics [0 ]} = \" { target_values [1 ]} \" \n \n "
1071
- f"return { metrics [0 ]} , prediction[0]"
1070
+ f"{ '' :4 } return { metrics [0 ]} , prediction[0]"
1072
1071
)
1073
1072
# Return classification and probability value
1074
1073
elif sum (returns ) == 1 and len (returns ) == 2 :
1075
- cls .score_code += f"\n \n return prediction[0], prediction[1]"
1074
+ cls .score_code += f"{ '' :4 } return prediction[0], prediction[1]"
1076
1075
elif sum (returns ) == 1 and len (returns ) == 3 :
1077
1076
warn (
1078
1077
"Due to the ambiguity of the provided metrics and prediction return"
@@ -1082,17 +1081,17 @@ def _binary_target(
1082
1081
# Determine which return is the classification value
1083
1082
class_index = [i for i , x in enumerate (returns ) if x ][0 ]
1084
1083
if class_index == 0 :
1085
- cls .score_code += f"\n \n return prediction[0], prediction[1]"
1084
+ cls .score_code += f"{ '' :4 } return prediction[0], prediction[1]"
1086
1085
else :
1087
1086
cls .score_code += (
1088
- f"\n \n return prediction[{ class_index } ], prediction[0]"
1087
+ f"{ '' :4 } return prediction[{ class_index } ], prediction[0]"
1089
1088
)
1090
1089
else :
1091
1090
cls ._invalid_predict_config ()
1092
1091
elif len (metrics ) == 3 :
1093
1092
if h2o_model :
1094
1093
cls .score_code += (
1095
- f"\n \n return prediction[1][0], prediction[1][1], prediction[1][2]"
1094
+ f"{ '' :4 } return prediction[1][0], prediction[1][1], prediction[1][2]"
1096
1095
)
1097
1096
elif sum (returns ) == 0 and len (returns ) == 1 :
1098
1097
warn (
@@ -1105,7 +1104,7 @@ def _binary_target(
1105
1104
f"{ '' :8} { metrics [0 ]} = \" { target_values [0 ]} \" \n "
1106
1105
f"{ '' :4} else:\n "
1107
1106
f"{ '' :8} { metrics [0 ]} = \" { target_values [1 ]} \" \n \n "
1108
- f"return { metrics [0 ]} , prediction, 1 - prediction"
1107
+ f"{ '' :4 } return { metrics [0 ]} , prediction, 1 - prediction"
1109
1108
)
1110
1109
elif sum (returns ) == 0 and len (returns ) == 2 :
1111
1110
warn (
@@ -1118,24 +1117,24 @@ def _binary_target(
1118
1117
f"{ '' :8} { metrics [0 ]} = \" { target_values [0 ]} \" \n "
1119
1118
f"{ '' :4} else:\n "
1120
1119
f"{ '' :8} { metrics [0 ]} = \" { target_values [1 ]} \" \n \n "
1121
- f"return { metrics [0 ]} , prediction[0], prediction[1]"
1120
+ f"{ '' :4 } return { metrics [0 ]} , prediction[0], prediction[1]"
1122
1121
)
1123
1122
# Find which return is the classification, then return probabilities
1124
1123
elif sum (returns ) == 1 and len (returns ) == 2 :
1125
1124
# Determine which return is the classification value
1126
1125
class_index = [i for i , x in enumerate (returns ) if x ][0 ]
1127
1126
if class_index == 0 :
1128
1127
cls .score_code += (
1129
- f"\n \n return prediction[0], prediction[1], 1 - prediction[1]"
1128
+ f"{ '' :4 } return prediction[0], prediction[1], 1 - prediction[1]"
1130
1129
)
1131
1130
else :
1132
1131
cls .score_code += (
1133
- f"\n \n return prediction[1], prediction[0], 1 - prediction[0]"
1132
+ f"{ '' :4 } return prediction[1], prediction[0], 1 - prediction[0]"
1134
1133
)
1135
1134
# Return all values from prediction method
1136
1135
elif sum (returns ) == 1 and len (returns ) == 3 :
1137
1136
cls .score_code += (
1138
- f"\n \n return prediction[0], prediction[1], prediction[2]"
1137
+ f"{ '' :4 } return prediction[0], prediction[1], prediction[2]"
1139
1138
)
1140
1139
else :
1141
1140
cls ._invalid_predict_config ()
@@ -1181,20 +1180,20 @@ def _nonbinary_targets(
1181
1180
f"{ '' :4} target_values = { target_values } \n { '' :4} "
1182
1181
f"{ metrics } = target_values[prediction[1][1:]."
1183
1182
f"index(max(prediction[1][1:]))]\n { '' :4} "
1184
- f"return { metrics } "
1183
+ f"{ '' :4 } return { metrics } "
1185
1184
)
1186
1185
# One return that is the classification
1187
1186
elif len (returns ) == 1 :
1188
1187
cls .score_code += f"{ '' :4} { metrics } = prediction\n \n return { metrics } "
1189
1188
elif len (returns ) == len (target_values ):
1190
1189
cls .score_code += (
1191
1190
f"{ '' :4} target_values = { target_values } \n \n "
1192
- f"return target_values[prediction.index(max(prediction))]"
1191
+ f"{ '' :4 } return target_values[prediction.index(max(prediction))]"
1193
1192
)
1194
1193
elif len (returns ) == (len (target_values ) + 1 ):
1195
1194
# Determine which return is the classification value
1196
1195
class_index = [i for i , x in enumerate (returns ) if x ][0 ]
1197
- cls .score_code += f"\n \n return prediction[{ class_index } ]"
1196
+ cls .score_code += f"{ '' :4 } return prediction[{ class_index } ]"
1198
1197
else :
1199
1198
cls ._invalid_predict_config ()
1200
1199
elif len (metrics ) == 2 :
@@ -1203,19 +1202,19 @@ def _nonbinary_targets(
1203
1202
f"{ '' :4} target_values = { target_values } \n { '' :4} "
1204
1203
f"{ metrics } = target_values[prediction[1][1:]."
1205
1204
f"index(max(prediction[1][1:]))]\n { '' :4} "
1206
- f"return { metrics } , max(prediction[1][1:])"
1205
+ f"{ '' :4 } return { metrics } , max(prediction[1][1:])"
1207
1206
)
1208
1207
elif len (returns ) == len (target_values ):
1209
1208
cls .score_code += (
1210
1209
f"{ '' :4} target_values = { target_values } \n \n "
1211
- f"return target_values[prediction.index(max(prediction))], "
1210
+ f"{ '' :4 } return target_values[prediction.index(max(prediction))], "
1212
1211
f"max(prediction)"
1213
1212
)
1214
1213
elif len (returns ) == (len (target_values ) + 1 ):
1215
1214
# Determine which return is the classification value
1216
1215
class_index = [i for i , x in enumerate (returns ) if x ][0 ]
1217
1216
cls .score_code += (
1218
- f"\n \n return prediction[{ class_index } ], "
1217
+ f"{ '' :4 } return prediction[{ class_index } ], "
1219
1218
f"max(prediction[:{ class_index } ] + prediction[{ class_index + 1 } :])"
1220
1219
)
1221
1220
else :
@@ -1224,25 +1223,25 @@ def _nonbinary_targets(
1224
1223
if h2o_model :
1225
1224
if len (metrics ) == len (target_values ):
1226
1225
h2o_returns = [f"prediction[1][{ i + 1 } ]" for i in range (len (metrics ))]
1227
- cls .score_code += f"\n \n return { ', ' .join (h2o_returns )} "
1226
+ cls .score_code += f"{ '' :4 } return { ', ' .join (h2o_returns )} "
1228
1227
elif len (metrics ) == (len (target_values ) + 1 ):
1229
1228
h2o_returns = [f"prediction[1][{ i } ]" for i in range (len (metrics ))]
1230
- cls .score_code += f"\n \n return { ', ' .join (h2o_returns )} "
1229
+ cls .score_code += f"{ '' :4 } return { ', ' .join (h2o_returns )} "
1231
1230
elif (
1232
1231
len (metrics ) == len (target_values ) == len (returns ) and sum (returns ) == 0
1233
1232
) or (
1234
1233
len (metrics ) == (len (target_values ) + 1 ) == len (returns )
1235
1234
and sum (returns ) == 1
1236
1235
):
1237
1236
proba_returns = [f"prediction[{ i } ]" for i in range (len (returns ))]
1238
- cls .score_code += f"\n \n return { ', ' .join (proba_returns )} "
1237
+ cls .score_code += f"{ '' :4 } return { ', ' .join (proba_returns )} "
1239
1238
elif (len (metrics ) - 1 ) == len (returns ) == len (target_values ) and sum (
1240
1239
returns
1241
1240
) == 0 :
1242
1241
proba_returns = [f"prediction[{ i } ]" for i in range (len (returns ))]
1243
1242
cls .score_code += (
1244
1243
f"{ '' :4} target_values = { target_values } \n \n "
1245
- f"return target_values[prediction.index(max(prediction))], "
1244
+ f"{ '' :4 } return target_values[prediction.index(max(prediction))], "
1246
1245
f"{ ', ' .join (proba_returns )} "
1247
1246
)
1248
1247
else :
@@ -1454,15 +1453,19 @@ def _viya35_score_code_import(
1454
1453
"""
1455
1454
files = [
1456
1455
{
1457
- "name" : f"{ prefix } _score .py" ,
1456
+ "name" : f"score_ { prefix } .py" ,
1458
1457
"file" : cls .score_code ,
1459
1458
"role" : "score" ,
1460
1459
}
1461
1460
]
1462
1461
cls .upload_and_copy_score_resources (model_id , files )
1463
- mr .convert_python_to_ds2 (model_id )
1462
+ # The typeConversion endpoint is only valid for models with Python score code
1463
+ model = mr .get_model (model_id )
1464
+ model ["scoreCodeType" ] = "Python"
1465
+ model = mr .update_model (model )
1466
+ mr .convert_python_to_ds2 (model )
1464
1467
if score_cas :
1465
- model_contents = mr .get_model_contents (model_id )
1468
+ model_contents = mr .get_model_contents (model )
1466
1469
for file in model_contents :
1467
1470
if file .name == "score.sas" :
1468
1471
mas_code = mr .get (
@@ -1478,9 +1481,9 @@ def _viya35_score_code_import(
1478
1481
}
1479
1482
],
1480
1483
)
1481
- cas_code = cls .convert_mas_to_cas (mas_code , model_id )
1484
+ cas_code = cls .convert_mas_to_cas (mas_code , model )
1482
1485
cls .upload_and_copy_score_resources (
1483
- model_id ,
1486
+ model ,
1484
1487
[
1485
1488
{
1486
1489
"name" : CAS_CODE_NAME ,
0 commit comments