Skip to content

Commit

Permalink
refactor(frontend): generate administration IDs during stratification (
Browse files Browse the repository at this point in the history
…#513) (#533)

When a dataset doesn't contain an administration ID column, generate one during the stratification step. Assign a unique administration ID to each group by default.
  • Loading branch information
eatyourgreens authored Oct 2, 2024
1 parent cfbf563 commit 443ce24
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 27 deletions.
9 changes: 4 additions & 5 deletions frontend-v2/src/features/data/CreateDosingProtocols.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ const CreateDosingProtocols: FC<IDosingProtocols> = ({
(field) =>
field === "Amount" || state.normalisedFields.get(field) === "Amount",
);
const dosingRows: Row[] =
administrationIdField === "Group ID"
? state.data
: // ignore rows with no amount and administration ID set to 0.
state.data.filter((row) => parseInt(row[administrationIdField]));
// ignore rows with no amount and administration ID set to 0.
const dosingRows: Row[] = state.data.filter((row) =>
parseInt(row[administrationIdField]),
);
if (!amountField) {
const newNormalisedFields = new Map([
...state.normalisedFields.entries(),
Expand Down
4 changes: 2 additions & 2 deletions frontend-v2/src/features/data/LoadData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,14 @@ const LoadData: FC<ILoadDataProps> = ({ state, firstTime }) => {
normalisedFields,
normalisedHeaders: [...normalisedFields.values()],
});
const primaryCohort =
const groupColumn =
fields.find(
(field) => normalisedFields.get(field) === "Cat Covariate",
) || "Group";
const errors = csvData.errors
.map((e) => e.message)
.concat(fieldValidation.errors);
state.setPrimaryCohort(primaryCohort);
state.setGroupColumn(groupColumn);
state.setErrors(errors);
state.setWarnings(fieldValidation.warnings);
if (csvData.data.length > 0 && csvData.meta.fields) {
Expand Down
10 changes: 5 additions & 5 deletions frontend-v2/src/features/data/LoadDataStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export type StepperState = {
setWarnings: (warnings: string[]) => void;
amountUnit?: string;
setAmountUnit: (amountUnit: string) => void;
primaryCohort: string;
setPrimaryCohort: (primaryCohort: string) => void;
groupColumn: string;
setGroupColumn: (primaryCohort: string) => void;
};

function validateSubjectProtocols(protocols: IProtocol[]) {
Expand Down Expand Up @@ -96,7 +96,7 @@ const LoadDataStepper: FC<IStepper> = ({ csv = "", onCancel, onFinish }) => {
);
const [timeUnit, setTimeUnit] = useState<string | undefined>(undefined);
const [amountUnit, setAmountUnit] = useState<string | undefined>(undefined);
const [primaryCohort, setPrimaryCohort] = useState("Group");
const [groupColumn, setGroupColumn] = useState("Group");
const selectedProject = useSelector(
(state: RootState) => state.main.selectedProject,
);
Expand All @@ -121,8 +121,8 @@ const LoadDataStepper: FC<IStepper> = ({ csv = "", onCancel, onFinish }) => {
setTimeUnit,
amountUnit,
setAmountUnit,
primaryCohort,
setPrimaryCohort,
groupColumn,
setGroupColumn,
get fields() {
return [...normalisedFields.keys()];
},
Expand Down
2 changes: 1 addition & 1 deletion frontend-v2/src/features/data/MapDosing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const MapDosing: FC<IMapDosing> = ({ state, firstTime }: IMapDosing) => {
/>
) : (
<CreateDosingProtocols
administrationIdField={administrationIdField || "Group ID"}
administrationIdField={administrationIdField || "Administration ID"}
amountUnitField={amountUnitField || ""}
amountUnit={amountUnit}
state={state}
Expand Down
61 changes: 47 additions & 14 deletions frontend-v2/src/features/data/Stratification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ function validateGroupProtocols(groups: Group[], protocols: IProtocol[]) {
return groupedProtocols.every((protocols) => protocols.length <= 1);
}

/**
* Generate a unique administration ID for each group.
* @param data
* @returns data with administration ID column.
*/
function generateAdministrationIds(data: { [key: string]: string }[]) {
const newData = data.map((row) => ({ ...row }));
const uniqueGroupIds = [...new Set(data.map((row) => row["Group ID"]))];
newData.forEach((row) => {
const administrationId = uniqueGroupIds.indexOf(row["Group ID"]) + 1;
row["Administration ID"] = `${administrationId}`;
});
return newData;
}

/**
* Assign a group ID to each row based on a categorical covariate column.
* @param data
* @param columnName
* @returns data with group ID and administration ID columns.
*/
function groupDataRows(data: { [key: string]: string }[], columnName: string) {
const newData = data.map((row) => ({ ...row }));
newData.forEach((row) => {
row["Group ID"] = row[columnName] || "1";
});
return newData;
}

interface IStratification {
state: StepperState;
firstTime: boolean;
Expand All @@ -55,12 +84,15 @@ const Stratification: FC<IStratification> = ({ state }: IStratification) => {
const values = state.data.map((row) => row[field]);
return [...new Set(values)];
});
const administrationIdField = state.fields.find(
(field) => state.normalisedFields.get(field) === "Administration ID",
);

const [firstRow] = state.data;
const [tab, setTab] = useState(0);
const { primaryCohort, setPrimaryCohort } = state;
const { groupColumn, setGroupColumn } = state;

const groups = groupsFromCatCovariate(state, primaryCohort);
const groups = groupsFromCatCovariate(state, groupColumn);
const isValidGrouping = validateGroupMembers(groups);
const groupErrorMessage =
"Invalid group subjects. Each subject ID can only belong to a single cohort.";
Expand Down Expand Up @@ -92,10 +124,10 @@ const Stratification: FC<IStratification> = ({ state }: IStratification) => {
}

if (!firstRow["Group ID"]) {
const newData = [...state.data];
newData.forEach((row) => {
row["Group ID"] = row[primaryCohort] || "1";
});
let newData = groupDataRows(state.data, groupColumn);
if (!administrationIdField) {
newData = generateAdministrationIds(newData);
}
state.setData(newData);
state.setNormalisedFields(
new Map([...state.normalisedFields.entries(), ["Group ID", "Group ID"]]),
Expand All @@ -106,12 +138,13 @@ const Stratification: FC<IStratification> = ({ state }: IStratification) => {
setTab(newValue);
};

const handlePrimaryChange = (event: ChangeEvent<HTMLInputElement>) => {
setPrimaryCohort(event.target.value);
const newData = [...state.data];
newData.forEach((row) => {
row["Group ID"] = row[event.target.value];
});
const handleGroupChange = (event: ChangeEvent<HTMLInputElement>) => {
const newGroup = event.target.value;
let newData = groupDataRows(state.data, newGroup);
if (!administrationIdField) {
newData = generateAdministrationIds(newData);
}
setGroupColumn(newGroup);
state.setData(newData);
};

Expand Down Expand Up @@ -147,7 +180,7 @@ const Stratification: FC<IStratification> = ({ state }: IStratification) => {
<TableBody>
{catCovariates.map((field, index) => {
const primaryLabel = `heading-primary field-${field}`;
const isPrimary = primaryCohort === field;
const isPrimary = groupColumn === field;
return (
<TableRow key={field}>
<TableCell id={`field-${field}`}>{field}</TableCell>
Expand All @@ -159,7 +192,7 @@ const Stratification: FC<IStratification> = ({ state }: IStratification) => {
name="primary"
value={field}
checked={isPrimary}
onChange={handlePrimaryChange}
onChange={handleGroupChange}
inputProps={{ "aria-labelledby": primaryLabel }}
/>
</TableCell>
Expand Down

0 comments on commit 443ce24

Please sign in to comment.