| | """Unit tests for the logic_consecutive_negative_responses function.""" |
| |
|
| | import copd |
| | import numpy as np |
| | import pandas as pd |
| | import pytest |
| |
|
| |
|
| | @pytest.fixture |
| | def exacerbation_event(): |
| | """Dataframe index (27) of the exacerbation event of interest.""" |
| | return 27 |
| |
|
| |
|
| | @pytest.fixture |
| | def first_pro_response(): |
| | """Dataframe index (8) of the first weekly PRO response.""" |
| | return 8 |
| |
|
| |
|
| | @pytest.fixture |
| | def second_pro_response(first_pro_response): |
| | """Dataframe index of the second weekly PRO response. Seven days after first.""" |
| | return first_pro_response + 7 |
| |
|
| |
|
| | @pytest.fixture |
| | def third_pro_response(second_pro_response): |
| | """Dataframe index of the third weekly PRO response. Seven days after second.""" |
| | return second_pro_response + 7 |
| |
|
| |
|
| | @pytest.fixture |
| | def input_df(exacerbation_event): |
| | """Sample input dataframe template - specific cases to be added in each test. |
| | |
| | This initial dataframe has no PRO responses between the initial exacerbation at index |
| | 2 and the event of interest with DaysSinceLastExac=25 at index exacerbation_event (set |
| | to 27). Interim PRO responses should be added in tests. Each row is a different day |
| | (in chronological order). Add/subtract N from exacerbation_event to refer to N days |
| | before or after the event by the dataframe index, e.g. exacerbation_event - 7 refers |
| | to the day a week prior. |
| | """ |
| | df = pd.DataFrame({'PatientId': ['1'] * 31, |
| | 'DateOfEvent': pd.date_range('2022-01-01', '2022-01-31'), |
| | 'Q5Answered': [0] * 31, |
| | 'NegativeQ5': [np.nan] * 31, |
| | 'DaysSinceLastExac': [-1, -1, -1] + list(np.arange(1, 26)) + |
| | list(np.arange(1, 4))}) |
| | |
| | df.loc[2, 'Q5Answered'] = 1 |
| | df.loc[2, 'NegativeQ5'] = 0 |
| | |
| | df.loc[exacerbation_event, 'Q5Answered'] = 1 |
| | df.loc[exacerbation_event, 'NegativeQ5'] = 0 |
| |
|
| | |
| | df.loc[exacerbation_event + 2, 'Q5Answered'] = 1 |
| | df.loc[exacerbation_event + 2, 'NegativeQ5'] = 1 |
| | return df |
| |
|
| |
|
| | def test_returns_one_when_no_responses(input_df, exacerbation_event): |
| | """Verify returns 1 (flag for removal) for no interim PRO responses.""" |
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 1 |
| |
|
| |
|
| | def test_returns_one_too_few_responses(input_df, exacerbation_event): |
| | """Verify returns 1 (flag for removal) for too few interim PRO responses.""" |
| | |
| | |
| | input_df.loc[exacerbation_event - 7, 'Q5Answered'] = 1 |
| | input_df.loc[exacerbation_event - 7, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 1 |
| |
|
| |
|
| | def test_returns_one_too_few_negative_responses( |
| | input_df, exacerbation_event, second_pro_response, third_pro_response): |
| | """Verify returns 1 (flag for removal) for too few interim PRO responses.""" |
| | |
| | |
| |
|
| | input_df.loc[second_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[second_pro_response, 'NegativeQ5'] = 0 |
| |
|
| | input_df.loc[third_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[third_pro_response, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 1 |
| |
|
| |
|
| | def test_returns_one_too_few_consecutive_negative_responses_missing( |
| | input_df, exacerbation_event, first_pro_response, second_pro_response, |
| | third_pro_response): |
| | """Verify returns 1 (flag for removal) for too few consecutive -ve PRO responses. |
| | |
| | Input has a missing response between the two negative responses. |
| | """ |
| | |
| | input_df.loc[first_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[first_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[third_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[third_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 1 |
| |
|
| |
|
| | def test_returns_one_too_few_consecutive_negative_responses_positive( |
| | input_df, exacerbation_event, first_pro_response, second_pro_response, |
| | third_pro_response): |
| | """Verify returns 1 (flag for removal) for too few consecutive -ve PRO responses. |
| | |
| | Input has a positive response between the two negative responses. |
| | """ |
| | |
| | input_df.loc[first_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[first_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[second_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[second_pro_response, 'NegativeQ5'] = 0 |
| |
|
| | input_df.loc[third_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[third_pro_response, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 1 |
| |
|
| |
|
| | def test_returns_zero_enough_consecutive_negative_responses_default( |
| | input_df, exacerbation_event, first_pro_response, second_pro_response): |
| | """Verify returns 0 (pass LOGIC criterion) for required consecutive -ve PRO responses. |
| | |
| | Input has two consecutive negative responses. Should return 1 with default options. |
| | """ |
| | |
| | input_df.loc[first_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[first_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[second_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[second_pro_response, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses(input_df, exacerbation_event) == 0 |
| |
|
| |
|
| | def test_returns_one_too_few_consecutive_negative_responses_non_default( |
| | input_df, exacerbation_event, first_pro_response, second_pro_response): |
| | """Verify returns 1 (flag for removal) for too few consecutive -ve PRO responses. |
| | |
| | Input has two consecutive negative responses. Should return 0 with N=3. |
| | """ |
| | |
| | input_df.loc[first_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[first_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[second_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[second_pro_response, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses( |
| | input_df, exacerbation_event, N=3) == 1 |
| |
|
| |
|
| | def test_returns_zero_too_few_consecutive_negative_responses_non_default( |
| | input_df, exacerbation_event, first_pro_response, second_pro_response, |
| | third_pro_response): |
| | """Verify returns 0 (pass LOGIC criterion) for required consecutive -ve PRO responses. |
| | |
| | Input has three consecutive negative responses. Should return 0 with N=3 |
| | """ |
| | |
| | input_df.loc[first_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[first_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[second_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[second_pro_response, 'NegativeQ5'] = 1 |
| |
|
| | input_df.loc[third_pro_response, 'Q5Answered'] = 1 |
| | input_df.loc[third_pro_response, 'NegativeQ5'] = 1 |
| | assert copd.logic_consecutive_negative_responses( |
| | input_df, exacerbation_event, N=3) == 0 |
| |
|