Repository: deepchecks/deepchecks

Issue:
[FEAT] auto infer features when dataframe is passed to check + warning
**Is your feature request related to a problem? Please describe.**
Currently when a df is passed to a check all of its columns are treated as non features, this behaviour is different than the behaviour when creating a "plain" dataset from a data frame.

**Describe the solution you'd like**
when a df is passed to a check will create a dataset from it in the same default manner as when creating a "plain" dataset. As discussed this behavior should include auto infer of features.
In addition should add a warning when a df is passed, explaining that categorical features will be inferred and explaining / linking to how to define a dataset.



Discussion:

Resolving patch:
diff --git a/deepchecks/tabular/dataset.py b/deepchecks/tabular/dataset.py
--- a/deepchecks/tabular/dataset.py
+++ b/deepchecks/tabular/dataset.py
@@ -886,7 +886,11 @@ def cast_to_dataset(cls, obj: t.Any) -> 'Dataset':
             if the provided value cannot be transformed into Dataset instance;
         """
         if isinstance(obj, pd.DataFrame):
-            obj = Dataset(obj, features=[], cat_features=[])
+            logger.warning(
+                'Received a "pandas.DataFrame" instance, initializing '
+                '"deepchecks.tabular.Dataset" from it'
+            )
+            obj = Dataset(obj)
         elif not isinstance(obj, Dataset):
             raise DeepchecksValueError(
                 f'non-empty instance of Dataset or DataFrame was expected, instead got {type(obj).__name__}'
diff --git a/deepchecks/utils/features.py b/deepchecks/utils/features.py
--- a/deepchecks/utils/features.py
+++ b/deepchecks/utils/features.py
@@ -473,10 +473,19 @@ def is_categorical(
     bool
         True if is categorical according to input numbers
     """
+    if len(column) == 0:
+        raise ValueError(
+            '"column" instance is empty, cannot determine '
+            'whether it is categorical or not'
+        )
+
     n_unique = column.nunique(dropna=True)
     n_samples = len(column.dropna())
 
     if is_float_dtype(column):
         return n_unique <= max_float_categories
 
-    return n_unique / n_samples < max_categorical_ratio and n_unique <= max_categories
+    if n_samples == 0:
+        return False
+
+    return (n_unique / n_samples) < max_categorical_ratio and n_unique <= max_categories


Test patch:
diff --git a/tests/base/dataset_test.py b/tests/base/dataset_test.py
--- a/tests/base/dataset_test.py
+++ b/tests/base/dataset_test.py
@@ -16,8 +16,8 @@
 import pandas as pd
 import pytest
 from hamcrest import (all_of, assert_that, calling, contains_exactly, equal_to,
-                      has_item, has_length, has_property, instance_of, is_,
-                      not_none, raises)
+                      greater_than, has_item, has_length, has_property,
+                      instance_of, is_, not_none, raises)
 from sklearn.datasets import load_iris, make_classification
 
 from deepchecks.core.errors import DeepchecksValueError
@@ -842,7 +842,7 @@ def test__ensure_not_empty_dataset__with_dataframe(iris: pd.DataFrame):
     ds = Dataset.cast_to_dataset(iris)
     # Assert
     assert_that(ds, instance_of(Dataset))
-    assert_that(ds.features, has_length(0))
+    assert_that(ds.features, has_length(greater_than(0)))
     assert_that(ds.label_name, equal_to(None))
     assert_that(ds.n_samples, equal_to(len(iris)))
 
diff --git a/tests/base/to_wandb_test.py b/tests/base/to_wandb_test.py
--- a/tests/base/to_wandb_test.py
+++ b/tests/base/to_wandb_test.py
@@ -9,9 +9,9 @@
 # ----------------------------------------------------------------------------
 #
 """to wandb tests"""
+import wandb
 from hamcrest import assert_that, equal_to, not_none
 
-import wandb
 from deepchecks.tabular.suites import full_suite
 
 wandb.setup(wandb.Settings(mode="disabled", program=__name__, program_relpath=__name__, disable_code=True))
diff --git a/tests/checks/integrity/string_mismatch_comparison_test.py b/tests/checks/integrity/string_mismatch_comparison_test.py
--- a/tests/checks/integrity/string_mismatch_comparison_test.py
+++ b/tests/checks/integrity/string_mismatch_comparison_test.py
@@ -82,18 +82,6 @@ def test_no_mismatch_on_numeric_string_column():
     assert_that(result, has_length(0))
 
 
-def test_no_mismatch_on_one_column_numeric():
-    # Arrange
-    data = {'num_str': ['10', '2.3', '1']}
-    compared_data = {'num_str': [1, 2.30, 1.0]}
-
-    # Act
-    result = StringMismatchComparison().run(pd.DataFrame(data=data), pd.DataFrame(data=compared_data)).value
-
-    # Assert
-    assert_that(result, has_length(0))
-
-
 def test_condition_no_new_variants_fail():
     # Arrange
     data = {'col1': ['Deep', 'deep', 'deep!!!', 'earth', 'foo', 'bar', 'foo?']}
diff --git a/tests/checks/overview/columns_info_test.py b/tests/checks/overview/columns_info_test.py
--- a/tests/checks/overview/columns_info_test.py
+++ b/tests/checks/overview/columns_info_test.py
@@ -48,9 +48,15 @@ def test_columns_info():
                        'b': 'numerical feature', 'c': 'other', 'label': 'label'}
     assert_that(result_ds, equal_to(expected_res_ds))
 
+    expected_res_df = {
+        'index': 'numerical feature', 
+        'date': 'numerical feature', 
+        'a': 'categorical feature', 
+        'b': 'numerical feature', 
+        'c': 'numerical feature', 
+        'label': 'categorical feature'
+    }
     # in df all columns are other
-    expected_res_df = {'index': 'other', 'date': 'other', 'a': 'other',
-                       'b': 'other', 'c': 'other', 'label': 'other'}
     assert_that(result_df, equal_to(expected_res_df))
 
 
diff --git a/tests/vision/checks/performance/robustness_report_test.py b/tests/vision/checks/performance/robustness_report_test.py
--- a/tests/vision/checks/performance/robustness_report_test.py
+++ b/tests/vision/checks/performance/robustness_report_test.py
@@ -12,10 +12,10 @@
 
 import albumentations
 import numpy as np
-from hamcrest import (assert_that, calling, close_to, has_entries, has_items, has_length,
-                      raises)
-from ignite.metrics import Precision
 import torchvision.transforms as T
+from hamcrest import (assert_that, calling, close_to, has_entries, has_items,
+                      has_length, raises)
+from ignite.metrics import Precision
 from PIL import Image
 
 from deepchecks.core.errors import DeepchecksValueError
diff --git a/tests/vision/docs/test_plots.py b/tests/vision/docs/test_plots.py
--- a/tests/vision/docs/test_plots.py
+++ b/tests/vision/docs/test_plots.py
@@ -12,7 +12,6 @@
 from runpy import run_path
 
 import torch
-
 import wandb
 
 # Since we have a plot that's calling `wandb.login` we need to setup first
diff --git a/tests/vision/vision_conftest.py b/tests/vision/vision_conftest.py
--- a/tests/vision/vision_conftest.py
+++ b/tests/vision/vision_conftest.py
@@ -14,21 +14,23 @@
 import numpy as np
 import pytest
 import torch
+import torchvision.transforms as T
 from PIL import Image
 from torch.utils.data import DataLoader, Dataset
 from torch.utils.data.dataloader import default_collate
-import torchvision.transforms as T
 from torchvision import datasets
 
 from deepchecks.core import DatasetKind
 from deepchecks.vision import Batch, Context, VisionData
-from deepchecks.vision.datasets.classification.mnist import MODULE_DIR as mnist_dir
+from deepchecks.vision.datasets.classification.mnist import \
+    MODULE_DIR as mnist_dir
 from deepchecks.vision.datasets.classification.mnist import MNISTData
 from deepchecks.vision.datasets.classification.mnist import \
     load_dataset as load_mnist_dataset
 from deepchecks.vision.datasets.classification.mnist import \
     load_model as load_mnist_net_model
-from deepchecks.vision.datasets.detection.coco import DATA_DIR as coco_root, LABEL_MAP as coco_labels
+from deepchecks.vision.datasets.detection.coco import DATA_DIR as coco_root
+from deepchecks.vision.datasets.detection.coco import LABEL_MAP as coco_labels
 from deepchecks.vision.datasets.detection.coco import COCOData, CocoDataset
 from deepchecks.vision.datasets.detection.coco import \
     load_dataset as load_coco_dataset