Repository: pyjanitor-devs/pyjanitor

Issue:
change_type, converting exception 
add an argument ignore_exception if one value of  a column can not be converted. 
ignore_exception can be equal to : 

- False  (default)  to return exception

- Fill_NaN to replace non convertible values with NaN

- Keep_value to keep the same value when the conversion is not possible 

@mdini



Discussion:
@mdini will take care of this. It's a great idea!

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -1181,9 +1181,14 @@ def remove_columns(df: pd.DataFrame, columns: List):
 
 
 @pf.register_dataframe_method
-def change_type(df, column: str, dtype):
+def change_type(df, column: str, dtype, ignore_exception = False):
     """
     Changes the type of a column.
+    
+    Exceptions that are raised can be ignored. For example, if one has a mixed
+    dtype column that has non-integer strings and integers, and you want to coerce
+    everything to integers, you can optionally ignore the non-integer strings and 
+    replace them with ``NaN``s or keep the original value
 
     Intended to be the method-chaining alternative to::
 
@@ -1199,8 +1204,22 @@ def change_type(df, column: str, dtype):
     :param column: A column in the dataframe.
     :param dtype: The datatype to convert to. Should be one of the standard
         Python types, or a numpy datatype.
-    """
-    df[column] = df[column].astype(dtype)
+    :param ignore_exception: one of {False, "fillna", "keep_values"}.
+    """
+    if not ignore_exception:
+        df[column] = df[column].astype(dtype)
+    elif ignore_exception == "keep_values":
+        df[column] = df[column].astype(dtype, errors="ignore" )
+    elif ignore_exception == "fillna":
+        # returns None when conversion 
+        def convert(x, dtype):
+            try: 
+                return dtype(x)
+            except:
+                return None
+        df[column] = df[column].apply(lambda x: convert(x, dtype)) ###
+    else:
+        raise ValueError("unknown option for ignore_exception")  
     return df
 
 


Test patch:
diff --git a/tests/functions/test_change_type.py b/tests/functions/test_change_type.py
--- a/tests/functions/test_change_type.py
+++ b/tests/functions/test_change_type.py
@@ -1,7 +1,38 @@
-import pytest
+import pytest 
+import pandas as pd
+import numpy as np 
 
 
 @pytest.mark.functions
 def test_change_type(dataframe):
     df = dataframe.change_type(column="a", dtype=float)
     assert df["a"].dtype == float
+    
+    
+
+@pytest.mark.functions
+def test_change_type_keep_values():
+    df=pd.DataFrame(['a',1,True], columns=['col1'])
+    df = df.change_type(column="col1", dtype=float, ignore_exception="keep_values")
+    assert df.equals(pd.DataFrame(['a',1,True], columns=['col1']))
+    
+    
+@pytest.mark.functions
+def test_change_type_fillna():
+    df=pd.DataFrame(['a',1,True], columns=['col1'])
+    df = df.change_type(column="col1", dtype=float, ignore_exception="fillna")
+    assert np.isnan(df.col1[0])
+
+    
+@pytest.mark.functions
+def test_change_type_unknown_option():
+    df=pd.DataFrame(['a',1,True], columns=['col1'])
+    with pytest.raises(Exception):
+        df = df.change_type(column="col1", dtype=float, ignore_exception="blabla")
+        
+        
+@pytest.mark.functions
+def test_change_type_raise_exception():
+    df=pd.DataFrame(['a',1,True], columns=['col1'])
+    with pytest.raises(Exception):
+        df = df.change_type(column="col1", dtype=float, ignore_exception=False)
\ No newline at end of file