Repository: eribean/girth

Issue:
Dichotomous JML issues
While working on #30, noticed Joint Maximum Likelihood had large errors, need to investigate and fix.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=h1) Report
> Merging [#84](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=desc) (a29c269) into [master](https://codecov.io/gh/eribean/girth/commit/194a4cb0122c97247fefa6a6fc7341776bfd56f5?el=desc) (194a4cb) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/eribean/girth/pull/84/graphs/tree.svg?width=650&height=150&src=pr&token=M7QW1P6V6X)](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master       #84   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           17        17           
  Lines         1213      1214    +1     
=========================================
+ Hits          1213      1214    +1     
```


| [Impacted Files](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [girth/utils.py](https://codecov.io/gh/eribean/girth/pull/84/diff?src=pr&el=tree#diff-Z2lydGgvdXRpbHMucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=footer). Last update [194a4cb...a29c269](https://codecov.io/gh/eribean/girth/pull/84?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/girth/utils.py b/girth/utils.py
index 7cb94c6..b981914 100644
--- a/girth/utils.py
+++ b/girth/utils.py
@@ -192,8 +192,11 @@ def trim_response_set_and_counts(response_sets, counts):
         response_set: updated response set with removal of undesired response patterns
         counts: updated counts to account for removal
     """
+    # Find any missing data
+    bad_mask = response_sets == INVALID_RESPONSE
+
     # Remove response sets where output is all true/false
-    mask = ~(np.nanvar(response_sets, axis=0) == 0)
+    mask = ~(np.ma.masked_array(response_sets, bad_mask).var(axis=0) == 0)
     response_sets = response_sets[:, mask]
     counts = counts[mask]
 


Test patch:
diff --git a/testing/test_missing_data.py b/testing/test_missing_data.py
index 8c321e5..52261f1 100644
--- a/testing/test_missing_data.py
+++ b/testing/test_missing_data.py
@@ -47,7 +47,7 @@ class TestMissingDichotomous(unittest.TestCase):
         result_all_good = rasch_jml(syn_data)
         result_missing = rasch_jml(syn_data_missing)
         difference_rmse = _rmse(result_all_good['Difficulty'], result_missing['Difficulty'])
-        self.assertAlmostEqual(difference_rmse, 0.100458, 4)
+        self.assertAlmostEqual(difference_rmse, 0.072384, 4)
 
         # MML
         result_all_good = rasch_mml(syn_data)
@@ -74,13 +74,13 @@ class TestMissingDichotomous(unittest.TestCase):
         syn_data_tagged = _create_missing_data(syn_data, 66877, 0.1)
         syn_data_missing = tag_missing_data(syn_data_tagged, [0, 1])
 
-        # JML (This is weirdly bad)
+        # JML 
         result_all_good = onepl_jml(syn_data)
         result_missing = onepl_jml(syn_data_missing)
         difference_rmse = _rmse(result_all_good['Difficulty'], result_missing['Difficulty'])
-        self.assertAlmostEqual(difference_rmse, 0.862138914, 4)        
+        self.assertAlmostEqual(difference_rmse, 0.048643, 4)        
         difference_rmse = _rmse(result_all_good['Discrimination'], result_missing['Discrimination'])
-        self.assertAlmostEqual(difference_rmse, 2.99675, 4)        
+        self.assertAlmostEqual(difference_rmse, 0.017526, 4)        
 
         # MML
         result_all_good = onepl_mml(syn_data)
@@ -99,7 +99,7 @@ class TestMissingDichotomous(unittest.TestCase):
         self.assertAlmostEqual(difference_rmse, 0.052542, 4)
 
     def test_twopl_jml_mml(self):
-        """Testing rasch mml/jml for missing data."""
+        """Testing twopl mml/jml for missing data."""
         np.random.seed(15335)
         n_items = 10
         n_people = 200
@@ -112,13 +112,13 @@ class TestMissingDichotomous(unittest.TestCase):
         syn_data_tagged = _create_missing_data(syn_data, 84433, 0.1)
         syn_data_missing = tag_missing_data(syn_data_tagged, [0, 1])
 
-        # JML (This is weirdly bad)
+        # JML 
         result_all_good = twopl_jml(syn_data)
         result_missing = twopl_jml(syn_data_missing)
         difference_rmse = _rmse(result_all_good['Difficulty'], result_missing['Difficulty'])
-        self.assertAlmostEqual(difference_rmse, 0.636390, 4)        
+        self.assertAlmostEqual(difference_rmse, 0.283604, 4)        
         difference_rmse = _rmse(result_all_good['Discrimination'], result_missing['Discrimination'])
-        self.assertAlmostEqual(difference_rmse, 2.027410, 4)        
+        self.assertAlmostEqual(difference_rmse, 0.669763, 4)        
 
         # MML
         result_all_good = twopl_mml(syn_data)
diff --git a/testing/test_utils.py b/testing/test_utils.py
index 27eb1f0..be1f362 100644
--- a/testing/test_utils.py
+++ b/testing/test_utils.py
@@ -106,17 +106,19 @@ class TestUtilitiesMethods(unittest.TestCase):
         np.testing.assert_array_equal(dataset[:, 1:-1], new_set)
         np.testing.assert_array_equal(counts[1:-1], new_counts)
 
-        # Test when array contains nans
-        mask = np.random.rand(*dataset.shape) < 0.1
-        dataset[mask] = np.nan
+        # Test when bad value is present
+        dataset = np.ones((10, 300), dtype=int)
+        dataset[0] = -1
+        dataset[0, 0] = INVALID_RESPONSE
+        counts = np.random.randint(0, 10, 300)
+        new_set, new_counts = trim_response_set_and_counts(dataset, counts)
+        self.assertEqual(new_set.shape[1], dataset.shape[1] - 1)
+        np.testing.assert_array_equal(dataset[:, 1:], new_set)
+        np.testing.assert_array_equal(counts[1:], new_counts)
+
+
 
-        # There are responses with zero variance
-        locations = np.where(np.nanstd(dataset, axis=0) == 0)
-        self.assertTrue(locations[0].size > 0)
 
-        new_set, new_counts = trim_response_set_and_counts(dataset, counts)
-        locations = np.where(np.nanstd(new_set, axis=0) == 0)
-        self.assertTrue(locations[0].size == 0)
 
     def test_get_true_false_counts(self):
         """Testing the counting of true and false."""