diff --git a/datacompy/core.py b/datacompy/core.py
index c6553f8..e39fa5e 100644
--- a/datacompy/core.py
+++ b/datacompy/core.py
@@ -62,6 +62,8 @@ class Compare(object):
         A string name for the second dataframe
     ignore_spaces : bool, optional
         Flag to strip whitespace (including newlines) from string columns
+    ignore_case : bool, optional
+        Flag to ignore the case of string columns
 
     Attributes
     ----------
@@ -82,6 +84,7 @@ class Compare(object):
         df1_name="df1",
         df2_name="df2",
         ignore_spaces=False,
+        ignore_case=False,
     ):
 
         if on_index and join_columns is not None:
@@ -105,7 +108,7 @@ class Compare(object):
         self.rel_tol = rel_tol
         self.df1_unq_rows = self.df2_unq_rows = self.intersect_rows = None
         self.column_stats = []
-        self._compare(ignore_spaces)
+        self._compare(ignore_spaces, ignore_case)
 
     @property
     def df1(self):
@@ -154,7 +157,7 @@ class Compare(object):
             if len(dataframe.drop_duplicates(subset=self.join_columns)) < len(dataframe):
                 self._any_dupes = True
 
-    def _compare(self, ignore_spaces):
+    def _compare(self, ignore_spaces, ignore_case):
         """Actually run the comparison.  This tries to run df1.equals(df2)
         first so that if they're truly equal we can tell.
 
@@ -176,7 +179,7 @@ class Compare(object):
         LOG.info("Number of columns in df2 and not in df1: {}".format(len(self.df2_unq_columns())))
         LOG.debug("Merging dataframes")
         self._dataframe_merge(ignore_spaces)
-        self._intersect_compare(ignore_spaces)
+        self._intersect_compare(ignore_spaces, ignore_case)
         if self.matches():
             LOG.info("df1 matches df2")
         else:
@@ -270,7 +273,7 @@ class Compare(object):
             )
         )
 
-    def _intersect_compare(self, ignore_spaces):
+    def _intersect_compare(self, ignore_spaces, ignore_case):
         """Run the comparison on the intersect dataframe
 
         This loops through all columns that are shared between df1 and df2, and
@@ -295,6 +298,7 @@ class Compare(object):
                     self.rel_tol,
                     self.abs_tol,
                     ignore_spaces,
+                    ignore_case,
                 )
                 match_cnt = self.intersect_rows[col_match].sum()
                 max_diff = calculate_max_diff(
@@ -585,7 +589,7 @@ def render(filename, *fields):
         return file_open.read().format(*fields)
 
 
-def columns_equal(col_1, col_2, rel_tol=0, abs_tol=0, ignore_spaces=False):
+def columns_equal(col_1, col_2, rel_tol=0, abs_tol=0, ignore_spaces=False, ignore_case=False):
     """Compares two columns from a dataframe, returning a True/False series,
     with the same index as column 1.
 
@@ -609,6 +613,8 @@ def columns_equal(col_1, col_2, rel_tol=0, abs_tol=0, ignore_spaces=False):
         Absolute tolerance
     ignore_spaces : bool, optional
         Flag to strip whitespace (including newlines) from string columns
+    ignore_case : bool, optional
+        Flag to ignore the case of string columns
 
     Returns
     -------
@@ -637,6 +643,12 @@ def columns_equal(col_1, col_2, rel_tol=0, abs_tol=0, ignore_spaces=False):
                     if col_2.dtype.kind == "O":
                         col_2 = col_2.str.strip()
 
+                if ignore_case:
+                    if col_1.dtype.kind == "O":
+                        col_1 = col_1.str.upper()
+                    if col_2.dtype.kind == "O":
+                        col_2 = col_2.str.upper()
+
                 if set([col_1.dtype.kind, col_2.dtype.kind]) == set(["M", "O"]):
                     compare = compare_string_and_date_columns(col_1, col_2)
                 else:

Ignore case when comparing strings
Might be a nice to have a optional ignore case feature so that `STRING` == `string`.
Should be relatively simple to add. Happy to take it on.

@theianrobertson thoughts?
diff --git a/tests/test_core.py b/tests/test_core.py
index 3251d37..7e37ac4 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -108,6 +108,28 @@ something||False
     assert_series_equal(expect_out, actual_out, check_names=False)
 
 
+def test_string_columns_equal_with_ignore_spaces_and_case():
+    data = """a|b|expected
+Hi|Hi|True
+Yo|Yo|True
+Hey|Hey |True
+résumé|resume|False
+résumé|résumé|True
+💩|💩|True
+💩|🤔|False
+ | |True
+  |       |True
+datacompy|DataComPy|True
+something||False
+|something|False
+||True"""
+    df = pd.read_csv(six.StringIO(data), sep="|")
+    actual_out = datacompy.columns_equal(df.a, df.b, rel_tol=0.2, ignore_spaces=True,
+                                         ignore_case=True)
+    expect_out = df["expected"]
+    assert_series_equal(expect_out, actual_out, check_names=False)
+
+
 def test_date_columns_equal():
     data = """a|b|expected
 2017-01-01|2017-01-01|True
@@ -158,6 +180,32 @@ def test_date_columns_equal_with_ignore_spaces():
     assert_series_equal(expect_out, actual_out_rev, check_names=False)
 
 
+def test_date_columns_equal_with_ignore_spaces_and_case():
+    data = """a|b|expected
+2017-01-01|2017-01-01   |True
+2017-01-02  |2017-01-02|True
+2017-10-01  |2017-10-10   |False
+2017-01-01||False
+|2017-01-01|False
+||True"""
+    df = pd.read_csv(six.StringIO(data), sep="|")
+    # First compare just the strings
+    actual_out = datacompy.columns_equal(df.a, df.b, rel_tol=0.2, ignore_spaces=True,
+                                         ignore_case=True)
+    expect_out = df["expected"]
+    assert_series_equal(expect_out, actual_out, check_names=False)
+
+    # Then compare converted to datetime objects
+    df["a"] = pd.to_datetime(df["a"])
+    df["b"] = pd.to_datetime(df["b"])
+    actual_out = datacompy.columns_equal(df.a, df.b, rel_tol=0.2, ignore_spaces=True)
+    expect_out = df["expected"]
+    assert_series_equal(expect_out, actual_out, check_names=False)
+    # and reverse
+    actual_out_rev = datacompy.columns_equal(df.b, df.a, rel_tol=0.2, ignore_spaces=True)
+    assert_series_equal(expect_out, actual_out_rev, check_names=False)
+
+
 def test_date_columns_unequal():
     """I want datetime fields to match with dates stored as strings
     """
@@ -323,6 +371,25 @@ def test_mixed_column_with_ignore_spaces():
     assert_series_equal(expect_out, actual_out, check_names=False)
 
 
+def test_mixed_column_with_ignore_spaces_and_case():
+    df = pd.DataFrame(
+        [
+            {"a": "hi", "b": "hi ", "expected": True},
+            {"a": 1, "b": 1, "expected": True},
+            {"a": np.inf, "b": np.inf, "expected": True},
+            {"a": Decimal("1"), "b": Decimal("1"), "expected": True},
+            {"a": 1, "b": "1 ", "expected": False},
+            {"a": 1, "b": "yo ", "expected": False},
+            {"a": "Hi", "b": "hI ", "expected": True},
+            {"a": "HI", "b": "HI ", "expected": True},
+            {"a": "hi", "b": "hi ", "expected": True},
+        ]
+    )
+    actual_out = datacompy.columns_equal(df.a, df.b, ignore_spaces=True, ignore_case=True)
+    expect_out = df["expected"]
+    assert_series_equal(expect_out, actual_out, check_names=False)
+
+
 def test_compare_df_setter_bad():
     df = pd.DataFrame([{"a": 1, "A": 2}, {"a": 2, "A": 2}])
     with raises(TypeError, message="df1 must be a pandas DataFrame"):
@@ -668,6 +735,23 @@ def test_strings_with_joins_with_ignore_spaces():
     assert compare.intersect_rows_match()
 
 
+
+def test_strings_with_joins_with_ignore_case():
+    df1 = pd.DataFrame([{"a": "hi", "b": "a"}, {"a": "bye", "b": "A"}])
+    df2 = pd.DataFrame([{"a": "hi", "b": "A"}, {"a": "bye", "b": "a"}])
+    compare = datacompy.Compare(df1, df2, "a", ignore_case=False)
+    assert not compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert not compare.intersect_rows_match()
+
+    compare = datacompy.Compare(df1, df2, "a", ignore_case=True)
+    assert compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert compare.intersect_rows_match()
+
+
 def test_decimal_with_joins_with_ignore_spaces():
     df1 = pd.DataFrame([{"a": 1, "b": " A"}, {"a": 2, "b": "A"}])
     df2 = pd.DataFrame([{"a": 1, "b": "A"}, {"a": 2, "b": "A "}])
@@ -684,6 +768,22 @@ def test_decimal_with_joins_with_ignore_spaces():
     assert compare.intersect_rows_match()
 
 
+def test_decimal_with_joins_with_ignore_case():
+    df1 = pd.DataFrame([{"a": 1, "b": "a"}, {"a": 2, "b": "A"}])
+    df2 = pd.DataFrame([{"a": 1, "b": "A"}, {"a": 2, "b": "a"}])
+    compare = datacompy.Compare(df1, df2, "a", ignore_case=False)
+    assert not compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert not compare.intersect_rows_match()
+
+    compare = datacompy.Compare(df1, df2, "a", ignore_case=True)
+    assert compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert compare.intersect_rows_match()
+
+
 def test_index_with_joins_with_ignore_spaces():
     df1 = pd.DataFrame([{"a": 1, "b": " A"}, {"a": 2, "b": "A"}])
     df2 = pd.DataFrame([{"a": 1, "b": "A"}, {"a": 2, "b": "A "}])
@@ -700,6 +800,22 @@ def test_index_with_joins_with_ignore_spaces():
     assert compare.intersect_rows_match()
 
 
+def test_index_with_joins_with_ignore_case():
+    df1 = pd.DataFrame([{"a": 1, "b": "a"}, {"a": 2, "b": "A"}])
+    df2 = pd.DataFrame([{"a": 1, "b": "A"}, {"a": 2, "b": "a"}])
+    compare = datacompy.Compare(df1, df2, on_index=True, ignore_case=False)
+    assert not compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert not compare.intersect_rows_match()
+
+    compare = datacompy.Compare(df1, df2, "a", ignore_case=True)
+    assert compare.matches()
+    assert compare.all_columns_match()
+    assert compare.all_rows_overlap()
+    assert compare.intersect_rows_match()
+
+
 MAX_DIFF_DF = pd.DataFrame(
     {
         "base": [1, 1, 1, 1, 1],

pip install -e .
parse_log_pytest
requirements.txt
pytest>=3.0.6 Sphinx>=1.6.2 sphinx-rtd-theme>=0.2.4 numpydoc>=0.6.0 mock>=2.0.0 pre-commit>=1.10.4 pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
cfgv==3.3.1
charset-normalizer==2.0.12
-e git+https://github.com/capitalone/datacompy.git@e406940f0c4b9acc6284ab27dcaecd96b857ae83#egg=datacompy
distlib==0.3.9
docutils==0.18.1
filelock==3.4.1
identify==2.4.4
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
mock==5.2.0
nodeenv==1.6.0
numpy==1.19.5
numpydoc==1.1.0
packaging==21.3
pandas==0.23.3
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
toml==0.10.2
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.16.2
zipp==3.6.0

name: datacompy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - cfgv==3.3.1
      - charset-normalizer==2.0.12
      - distlib==0.3.9
      - docutils==0.18.1
      - filelock==3.4.1
      - identify==2.4.4
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - mock==5.2.0
      - nodeenv==1.6.0
      - numpy==1.19.5
      - numpydoc==1.1.0
      - packaging==21.3
      - pandas==0.23.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - toml==0.10.2
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.16.2
      - zipp==3.6.0
prefix: /opt/conda/envs/datacompy

tests/test_core.py::test_string_columns_equal_with_ignore_spaces_and_case tests/test_core.py::test_date_columns_equal_with_ignore_spaces_and_case tests/test_core.py::test_mixed_column_with_ignore_spaces_and_case tests/test_core.py::test_strings_with_joins_with_ignore_case tests/test_core.py::test_decimal_with_joins_with_ignore_case tests/test_core.py::test_index_with_joins_with_ignore_case
tests/test_core.py::test_compare_df_setter_bad tests/test_core.py::test_compare_df_setter_bad_index tests/test_core.py::test_compare_on_index_and_join_columns
tests/test_core.py::test_numeric_columns_equal_abs tests/test_core.py::test_numeric_columns_equal_rel tests/test_core.py::test_string_columns_equal tests/test_core.py::test_string_columns_equal_with_ignore_spaces tests/test_core.py::test_date_columns_equal tests/test_core.py::test_date_columns_equal_with_ignore_spaces tests/test_core.py::test_date_columns_unequal tests/test_core.py::test_bad_date_columns tests/test_core.py::test_rounded_date_columns tests/test_core.py::test_decimal_float_columns_equal tests/test_core.py::test_decimal_float_columns_equal_rel tests/test_core.py::test_decimal_columns_equal tests/test_core.py::test_decimal_columns_equal_rel tests/test_core.py::test_infinity_and_beyond tests/test_core.py::test_mixed_column tests/test_core.py::test_mixed_column_with_ignore_spaces tests/test_core.py::test_compare_df_setter_good tests/test_core.py::test_compare_df_setter_different_cases tests/test_core.py::test_compare_df_setter_good_index tests/test_core.py::test_columns_overlap tests/test_core.py::test_columns_no_overlap tests/test_core.py::test_10k_rows tests/test_core.py::test_subset tests/test_core.py::test_not_subset tests/test_core.py::test_large_subset tests/test_core.py::test_string_joiner tests/test_core.py::test_decimal_with_joins tests/test_core.py::test_decimal_with_nulls tests/test_core.py::test_strings_with_joins tests/test_core.py::test_index_joining tests/test_core.py::test_index_joining_strings_i_guess tests/test_core.py::test_index_joining_non_overlapping tests/test_core.py::test_temp_column_name tests/test_core.py::test_temp_column_name_one_has tests/test_core.py::test_temp_column_name_both_have tests/test_core.py::test_temp_column_name_one_already tests/test_core.py::test_simple_dupes_one_field tests/test_core.py::test_simple_dupes_two_fields tests/test_core.py::test_simple_dupes_index tests/test_core.py::test_simple_dupes_one_field_two_vals tests/test_core.py::test_simple_dupes_one_field_three_to_two_vals tests/test_core.py::test_dupes_from_real_data tests/test_core.py::test_strings_with_joins_with_ignore_spaces tests/test_core.py::test_decimal_with_joins_with_ignore_spaces tests/test_core.py::test_index_with_joins_with_ignore_spaces tests/test_core.py::test_calculate_max_diff[base-0] tests/test_core.py::test_calculate_max_diff[floats-0.2] tests/test_core.py::test_calculate_max_diff[decimals-0.1] tests/test_core.py::test_calculate_max_diff[null_floats-0.1] tests/test_core.py::test_calculate_max_diff[strings-0.1] tests/test_core.py::test_calculate_max_diff[mixed_strings-0] tests/test_core.py::test_calculate_max_diff[infinity-inf]
datacompy/core.py
datacompy/core.py