Repository: mindsdb/mindsdb

Issue:
Timeseries order-by not working for date
@surendra1472  recently found this bug.

When the order-by column is a date, timeseries crashes because the column (order by column is this case) is split into 3 columns for year, month and day, but the sorting is still done on the original column.


Discussion:

Resolving patch:
diff --git a/mindsdb/libs/backends/ludwig.py b/mindsdb/libs/backends/ludwig.py
--- a/mindsdb/libs/backends/ludwig.py
+++ b/mindsdb/libs/backends/ludwig.py
@@ -47,7 +47,7 @@ def _translate_df_to_timeseries_format(self, df, model_definition, timeseries_co
         for feature_def in model_definition['input_features']:
             if feature_def['name'] not in self.transaction.lmd['model_group_by'] and feature_def['name'] not in previous_predict_col_names:
                 feature_def['type'] = 'sequence'
-                if feature_def['name'] not in timeseries_cols:
+                if feature_def['name'] not in [timeseries_col_name]:
                     other_col_names.append(feature_def['name'])
 
 
@@ -205,12 +205,12 @@ def _create_ludwig_dataframe(self, mode):
 
             if col in timeseries_cols:
                 encoder = 'rnn'
-                cell_type = 'gru_cudnn'
+                cell_type = 'rnn'
                 ludwig_dtype = 'order_by_col'
 
             if data_subtype in DATA_SUBTYPES.ARRAY:
                 encoder = 'rnn'
-                cell_type = 'gru_cudnn'
+                cell_type = 'rnn'
                 ludwig_dtype = 'sequence'
 
             elif data_subtype in (DATA_SUBTYPES.INT, DATA_SUBTYPES.FLOAT):
@@ -296,6 +296,12 @@ def _create_ludwig_dataframe(self, mode):
 
                     custom_logic_continue = True
 
+                    if col in timeseries_cols:
+                        timeseries_cols.remove(col)
+                        timeseries_cols.append(col + '_day')
+                        timeseries_cols.append(col + '_month')
+                        timeseries_cols.append(col + '_year')
+
                 elif data_subtype in (DATA_SUBTYPES.TIMESTAMP):
                     if self.transaction.input_data.data_array[row_ind][col_ind] is None:
                         unix_ts = 0
@@ -361,7 +367,7 @@ def _create_ludwig_dataframe(self, mode):
         if len(timeseries_cols) > 0:
             df.sort_values(timeseries_cols)
 
-        return df, model_definition
+        return df, model_definition, timeseries_cols
 
     def get_model_dir(self):
         model_dir = None
@@ -373,6 +379,8 @@ def get_model_dir(self):
         return model_dir
 
     def get_useable_gpus(self):
+        if self.transaction.lmd['use_gpu'] == False:
+            return []
         local_device_protos = device_lib.list_local_devices()
         gpus = [x for x in local_device_protos if x.device_type == 'GPU']
         #bus_ids = [x.locality.bus_id for x in gpus]
@@ -383,11 +391,7 @@ def get_useable_gpus(self):
             return gpu_indices
 
     def train(self):
-        training_dataframe, model_definition = self._create_ludwig_dataframe('train')
-        if self.transaction.lmd['model_order_by'] is None:
-            timeseries_cols = []
-        else:
-            timeseries_cols = list(map(lambda x: x[0], self.transaction.lmd['model_order_by']))
+        training_dataframe, model_definition, timeseries_cols = self._create_ludwig_dataframe('train')
 
         if len(timeseries_cols) > 0:
             training_dataframe, model_definition =  self._translate_df_to_timeseries_format(training_dataframe, model_definition, timeseries_cols, 'train')
@@ -448,14 +452,9 @@ def train(self):
         self.transaction.hmd['ludwig_data'] = {'model_definition': model_definition}
 
     def predict(self, mode='predict', ignore_columns=[]):
-        predict_dataframe, model_definition = self._create_ludwig_dataframe(mode)
+        predict_dataframe, model_definition, timeseries_cols = self._create_ludwig_dataframe(mode)
         model_definition = self.transaction.hmd['ludwig_data']['model_definition']
 
-        if self.transaction.lmd['model_order_by'] is None:
-            timeseries_cols = []
-        else:
-            timeseries_cols = list(map(lambda x: x[0], self.transaction.lmd['model_order_by']))
-
         if len(timeseries_cols) > 0:
             predict_dataframe, model_definition =  self._translate_df_to_timeseries_format(predict_dataframe, model_definition, timeseries_cols)
 
diff --git a/mindsdb/libs/controllers/predictor.py b/mindsdb/libs/controllers/predictor.py
--- a/mindsdb/libs/controllers/predictor.py
+++ b/mindsdb/libs/controllers/predictor.py
@@ -400,7 +400,7 @@ def delete_model(self, model_name):
 
     def learn(self, to_predict, from_data = None, test_from_data=None, group_by = None, window_size_samples = None, window_size_seconds = None,
     window_size = None, order_by = [], sample_margin_of_error = CONFIG.DEFAULT_MARGIN_OF_ERROR, ignore_columns = [], rename_strange_columns = False,
-    stop_training_in_x_seconds = None, stop_training_in_accuracy = None,  send_logs=CONFIG.SEND_LOGS, backend='ludwig', rebuild_model=True):
+    stop_training_in_x_seconds = None, stop_training_in_accuracy = None,  send_logs=CONFIG.SEND_LOGS, backend='ludwig', rebuild_model=True, use_gpu=True):
         """
         Tells the mind to learn to predict a column or columns from the data in 'from_data'
 
@@ -498,6 +498,7 @@ def learn(self, to_predict, from_data = None, test_from_data=None, group_by = No
         light_transaction_metadata['unusual_columns_buckets_importances'] = None
         light_transaction_metadata['columnless_prediction_distribution'] = None
         light_transaction_metadata['all_columns_prediction_distribution'] = None
+        light_transaction_metadata['use_gpu'] = use_gpu
         light_transaction_metadata['malformed_columns'] = {'names': [], 'indices': []}
 
 
@@ -522,7 +523,7 @@ def learn(self, to_predict, from_data = None, test_from_data=None, group_by = No
         Transaction(session=self, light_transaction_metadata=light_transaction_metadata, heavy_transaction_metadata=heavy_transaction_metadata, logger=self.log, breakpoint=breakpoint)
 
 
-    def predict(self, when={}, when_data = None, update_cached_model = False):
+    def predict(self, when={}, when_data = None, update_cached_model = False, use_gpu=True):
         """
         You have a mind trained already and you want to make a prediction
 
@@ -552,6 +553,7 @@ def predict(self, when={}, when_data = None, update_cached_model = False):
         light_transaction_metadata = {}
         light_transaction_metadata['name'] = self.name
         light_transaction_metadata['type'] = transaction_type
+        light_transaction_metadata['use_gpu'] = use_gpu
         light_transaction_metadata['data_preparation'] = {}
 
         transaction = Transaction(session=self, light_transaction_metadata=light_transaction_metadata, heavy_transaction_metadata=heavy_transaction_metadata, breakpoint=breakpoint)


Test patch:
diff --git a/integration_testing/run_tests.py b/integration_testing/run_tests.py
--- a/integration_testing/run_tests.py
+++ b/integration_testing/run_tests.py
@@ -45,7 +45,7 @@ def test_timeseries():
     logger.info('Starting timeseries test !')
     ts_hours = 12
     separator = ','
-    data_len = 1200
+    data_len = 600
     train_file_name = 'train_data.csv'
     test_file_name = 'test_data.csv'
 
@@ -54,9 +54,9 @@ def test_timeseries():
 
     try:
         # add ,'ascii' in the features list to re-implement the group by
-        features = generate_value_cols(['datetime','int','float', 'ascii'],data_len, separator, ts_hours * 3600)
-        features[3] = list(map(lambda x: str(x[0]) if len(x) > 0 else 'Nrmm', features[3]))
-        labels = [generate_labels_1(features, separator)]
+        features = generate_value_cols(['date','int'],data_len, separator, ts_hours * 3600)
+        #features[3] = list(map(lambda x: str(x[0]) if len(x) > 0 else 'Nrmm', features[3]))
+        labels = [generate_labels_2(features, separator)]
 
         feature_headers = list(map(lambda col: col[0], features))
         label_headers = list(map(lambda col: col[0], labels))
@@ -82,6 +82,7 @@ def test_timeseries():
         mdb = mindsdb.Predictor(name='test_date_timeseries')
         logger.debug(f'Succesfully create mindsdb Predictor')
     except:
+        print(traceback.format_exc())
         logger.error(f'Failed to create mindsdb Predictor')
         exit(1)
 
@@ -92,9 +93,10 @@ def test_timeseries():
             to_predict=label_headers
             # timeseries specific argsw
             ,order_by=feature_headers[0]
-            ,window_size_seconds=ts_hours* 3600 * 1.5
-            #,window_size=6
-            ,group_by = feature_headers[3]
+            #,window_size_seconds=ts_hours* 3600 * 1.5
+            ,window_size_samples=6
+            #,group_by = feature_headers[3]
+            ,use_gpu=False
         )
         logger.info(f'--------------- Learning ran succesfully ---------------')
     except:
@@ -112,7 +114,7 @@ def test_timeseries():
         exit(1)
 
     try:
-        results = mdb.predict(when_data=test_file_name)
+        results = mdb.predict(when_data=test_file_name,use_gpu=False)
         models = mdb.get_models()
         mdb.get_model_data(models[0]['name'])
         for row in results:
@@ -378,7 +380,7 @@ def test_multilabel_prediction():
 
 
 setup_testing_logger()
-test_one_label_prediction_wo_strings()
+#test_one_label_prediction_wo_strings()
 test_timeseries()
-test_multilabel_prediction()
-test_one_label_prediction()
+#test_multilabel_prediction()
+#test_one_label_prediction()