diff --git a/config/employmentTransStats.yml b/config/employmentTransStats.yml new file mode 100644 index 000000000..d068199a1 --- /dev/null +++ b/config/employmentTransStats.yml @@ -0,0 +1,64 @@ +# This file can be used to override defaults for multirun arguments. +# Arguments of the SimPathsMultiRun object overridden by the command-line + +maxNumberOfRuns: 1 +executeWithGui: false +randomSeed: 606 +startYear: 2011 +endYear: 2020 +popSize: 50000 + +# Arguments passed to the SimPathsModel +model_args: +# maxAge: 130 +# fixTimeTrend: true +# timeTrendStopsIn: 2017 +# fixRandomSeed: true +# sIndexTimeWindow: 5 +# sIndexAlpha: 2 +# sIndexDelta: 0 +# savingRate: 0 +# initialisePotentialEarningsFromDatabase: true +# useWeights: false +# useSBAMMatching: +# projectMortality: true +# alignFertility: true +# labourMarketCovid19On: false +# projectFormalChildcare: true +# donorPoolAveraging: true +# alignEmployment: false +# projectSocialCare: false +# enableIntertemporalOptimisations: true +# responsesToLowWageOffer: true +# saveImperfectTaxDBMatches: false +# useSavedBehaviour: false +# readGrid: "laptop serial" +# saveBehaviour: true +# employmentOptionsOfPrincipalWorker: 3 +# employmentOptionsOfSecondaryWorker: 3 +# responsesToEducation: true +# responsesToRetirement: false +# responsesToHealth: true +# responsesToDisability: false +# minAgeForPoorHealth: 50 +# responsesToRegion: false + +# Arguments that alter processing of the SimPathsMultiRun object +innovation_args: +# randomSeedInnov: false +# intertemporalElasticityInnov: false +# labourSupplyElasticityInnov: true + +collector_args: +# calculateGiniCoefficients: false +# exportToDatabase: false +# exportToCSV: true + persistStatistics: false + persistStatistics2: false + persistStatistics3: false + persistPersons: false + persistBenefitUnits: false + persistHouseholds: false + persistEmploymentStatistics: true +# dataDumpStartTime: 0L +# dataDumpTimePeriod: 1.0 \ No newline at end of file diff --git a/src/main/java/simpaths/data/filters/EmploymentHistoryFilter.java b/src/main/java/simpaths/data/filters/EmploymentHistoryFilter.java new file mode 100644 index 000000000..908d762e9 --- /dev/null +++ b/src/main/java/simpaths/data/filters/EmploymentHistoryFilter.java @@ -0,0 +1,27 @@ +package simpaths.data.filters; + +import microsim.statistics.ICollectionFilter; +import simpaths.model.Person; +import simpaths.model.enums.Gender; +import simpaths.model.enums.Les_c4; + +public class EmploymentHistoryFilter implements ICollectionFilter { + + private Les_c4 employmentLag1; + + public EmploymentHistoryFilter(Les_c4 employmentLag1) { + super(); + this.employmentLag1 = employmentLag1; + + } + + public boolean isFiltered(Object object) { + + if (object instanceof Person){ + Person person = (Person) object; + return (person.getLes_c4_lag1().equals(employmentLag1)); + } + else throw new IllegalArgumentException("Argument passed to EmploymentHistoryFilter must be of object type Person"); + } + +} diff --git a/src/main/java/simpaths/data/statistics/EmploymentStatistics.java b/src/main/java/simpaths/data/statistics/EmploymentStatistics.java new file mode 100644 index 000000000..86a5a614c --- /dev/null +++ b/src/main/java/simpaths/data/statistics/EmploymentStatistics.java @@ -0,0 +1,70 @@ +package simpaths.data.statistics; + +import jakarta.persistence.Column; +import jakarta.persistence.Id; +import jakarta.persistence.Entity; + +import microsim.data.db.PanelEntityKey; +import microsim.statistics.CrossSection; +import microsim.statistics.IDoubleSource; +import microsim.statistics.functions.MeanArrayFunction; +import simpaths.data.filters.EmploymentHistoryFilter; +import simpaths.model.SimPathsModel; +import simpaths.model.enums.Les_c4; +import simpaths.model.Person; + +@Entity +public class EmploymentStatistics { + + @Id + private PanelEntityKey key = new PanelEntityKey(1L); + + @Column(name= "EmpToNotEmp") + private double EmpToNotEmp; // Proportion of employed people becoming unemployed + + @Column(name= "NotEmpToEmp") + private double NotEmpToEmp; // Proportion of unemployed people becoming employed + + + public double getEmpToNotEmp() { + return EmpToNotEmp; + } + + public void setEmpToNotEmp(double empToNotEmp) { + EmpToNotEmp = empToNotEmp; + } + + public double getNotEmpToEmp() { + return NotEmpToEmp; + } + + public void setNotEmpToEmp(double notEmpToEmp) { + NotEmpToEmp = notEmpToEmp; + } + + public void update(SimPathsModel model) { + + EmploymentHistoryFilter employmentHistoryEmployed = new EmploymentHistoryFilter(Les_c4.EmployedOrSelfEmployed); + EmploymentHistoryFilter employmentHistoryUnemployed = new EmploymentHistoryFilter(Les_c4.NotEmployed); + + + // Entering employment transition rate + CrossSection.Integer personsNotEmpToEmp = new CrossSection.Integer(model.getPersons(), Person.class, "getEmployed", true); + personsNotEmpToEmp.setFilter(employmentHistoryUnemployed); + // Entering not employed transition rate + CrossSection.Integer personsEmpToNotEmp = new CrossSection.Integer(model.getPersons(), Person.class, "getNonwork", true); + personsEmpToNotEmp.setFilter(employmentHistoryEmployed); + + + MeanArrayFunction isNotEmpToEmp = new MeanArrayFunction(personsNotEmpToEmp); + isNotEmpToEmp.applyFunction(); + setNotEmpToEmp(isNotEmpToEmp.getDoubleValue(IDoubleSource.Variables.Default)); + + MeanArrayFunction isEmpToNotEmp = new MeanArrayFunction(personsEmpToNotEmp); + isEmpToNotEmp.applyFunction(); + setEmpToNotEmp(isEmpToNotEmp.getDoubleValue(IDoubleSource.Variables.Default)); + + + + } +} diff --git a/src/main/java/simpaths/experiment/SimPathsCollector.java b/src/main/java/simpaths/experiment/SimPathsCollector.java index 695fa23b6..df6636c2d 100644 --- a/src/main/java/simpaths/experiment/SimPathsCollector.java +++ b/src/main/java/simpaths/experiment/SimPathsCollector.java @@ -8,6 +8,7 @@ import java.util.Map; import simpaths.data.filters.FlexibleInLabourSupplyFilter; +import simpaths.data.statistics.EmploymentStatistics; import simpaths.model.BenefitUnit; import simpaths.model.SimPathsModel; import simpaths.model.enums.Quintiles; @@ -57,6 +58,8 @@ public class SimPathsCollector extends AbstractSimulationCollectorManager implem @GUIparameter(description="Report alignment adjustments") private boolean persistStatistics3 = true; + private boolean persistEmploymentStatistics = false; + @GUIparameter(description="Toggle to turn database persistence on/off") private boolean exportToDatabase = false; @@ -91,6 +94,8 @@ public class SimPathsCollector extends AbstractSimulationCollectorManager implem private Statistics3 stats3; + private EmploymentStatistics statsEmployment; + private GiniPersonalGrossEarnings giniPersonalGrossEarnings; private GiniEquivalisedHouseholdDisposableIncome giniEquivalisedHouseholdDisposableIncome; @@ -115,6 +120,8 @@ public class SimPathsCollector extends AbstractSimulationCollectorManager implem private DataExport exportStatistics3; + private DataExport exportStatisticsEmployment; + protected MultiTraceFunction.Double fGiniPersonalGrossEarningsNational; protected Map fGiniPersonalGrossEarningsRegionalMap; @@ -150,6 +157,7 @@ public enum Processes { DumpStatistics, DumpStatistics2, DumpStatistics3, + DumpStatisticsEmployment } @@ -218,6 +226,13 @@ public void onEvent(Enum type) { log.error(e.getMessage()); } break; + case DumpStatisticsEmployment: + statsEmployment.update(model); + try { + exportStatisticsEmployment.export(); + } catch (Exception e) { + log.error(e.getMessage()); + } } } @@ -234,6 +249,7 @@ public void buildObjects() { stats = new Statistics(); stats2 = new Statistics2(); stats3 = new Statistics3(); + statsEmployment = new EmploymentStatistics(); //For export to database or .csv files. if(persistPersons) @@ -248,6 +264,8 @@ public void buildObjects() { exportStatistics2 = new DataExport(stats2, exportToDatabase, exportToCSV); if (persistStatistics3) exportStatistics3 = new DataExport(stats3, exportToDatabase, exportToCSV); + if (persistEmploymentStatistics) + exportStatisticsEmployment = new DataExport(statsEmployment, exportToDatabase, exportToCSV); if (calculateGiniCoefficients) { @@ -308,6 +326,10 @@ public void buildSchedule() { getEngine().getEventQueue().scheduleRepeat(new SingleTargetEvent(this, Processes.DumpStatistics3), model.getStartYear() + dataDumpStartTime, ordering, dataDumpTimePeriod); } + if (persistEmploymentStatistics) { + getEngine().getEventQueue().scheduleRepeat(new SingleTargetEvent(this, Processes.DumpStatisticsEmployment), model.getStartYear() + dataDumpStartTime, ordering, dataDumpTimePeriod); + } + if (persistPersons) { getEngine().getEventQueue().scheduleRepeat(new SingleTargetEvent(this, Processes.DumpPersons), model.getStartYear() + dataDumpStartTime, ordering, dataDumpTimePeriod); } diff --git a/src/main/java/simpaths/model/Person.java b/src/main/java/simpaths/model/Person.java index 995f32883..b9ed7016e 100644 --- a/src/main/java/simpaths/model/Person.java +++ b/src/main/java/simpaths/model/Person.java @@ -3964,6 +3964,14 @@ public int getNonwork() { return (Les_c4.NotEmployed.equals(les_c4)) ? 1 : 0; } + public int getEmployed_Lag1() { + return (Les_c4.EmployedOrSelfEmployed.equals(les_c4_lag1)) ? 1 : 0; + } + + public int getNonwork_Lag1() { + return (Les_c4.NotEmployed.equals(les_c4_lag1)) ? 1 : 0; + } + public void setRegionLocal(Region region) { regionLocal = region; } diff --git a/src/test/java/simpaths/data/filters/EmploymentHistoryFilterTest.java b/src/test/java/simpaths/data/filters/EmploymentHistoryFilterTest.java new file mode 100644 index 000000000..5ea3780f5 --- /dev/null +++ b/src/test/java/simpaths/data/filters/EmploymentHistoryFilterTest.java @@ -0,0 +1,43 @@ +package simpaths.data.filters; + +import org.junit.jupiter.api.*; +import simpaths.model.Person; +import simpaths.model.enums.Les_c4; + + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Test filtering by employment history") +class EmploymentHistoryFilterTest { + + private static Person createTestPerson( + Les_c4 les_c4_lag1 + ) { + Person testPerson = new Person(true); + testPerson.setLes_c4_lag1(les_c4_lag1); + + return testPerson; + } + + @Test + @DisplayName("Employed filter only returns true for employed or self-employed persons") + void employedOrSelfEmployed() { + EmploymentHistoryFilter filter = new EmploymentHistoryFilter(Les_c4.EmployedOrSelfEmployed); + assertTrue(filter.isFiltered(createTestPerson(Les_c4.EmployedOrSelfEmployed))); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.NotEmployed))); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.Student))); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.Retired))); + } + + @Test + @DisplayName("Unemployed filter only returns true for unemployed persons") + void unEmployed() { + EmploymentHistoryFilter filter = new EmploymentHistoryFilter(Les_c4.NotEmployed); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.EmployedOrSelfEmployed))); + assertTrue(filter.isFiltered(createTestPerson(Les_c4.NotEmployed))); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.Student))); + assertFalse(filter.isFiltered(createTestPerson(Les_c4.Retired))); + } + + +} \ No newline at end of file diff --git a/src/test/java/simpaths/data/statistics/EmploymentStatisticsTest.java b/src/test/java/simpaths/data/statistics/EmploymentStatisticsTest.java new file mode 100644 index 000000000..e30a2c668 --- /dev/null +++ b/src/test/java/simpaths/data/statistics/EmploymentStatisticsTest.java @@ -0,0 +1,93 @@ +package simpaths.data.statistics; + +import microsim.statistics.CrossSection; +import microsim.statistics.IDoubleSource; +import microsim.statistics.functions.MeanArrayFunction; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import simpaths.data.filters.EmploymentHistoryFilter; +import simpaths.model.Person; +import simpaths.model.enums.Les_c4; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Calculating employment statistics") +class EmploymentStatisticsTest { + + private static List testPopulation; + + private static Person createTestPerson( + Les_c4 les_c4_lag1, + Les_c4 les_c4 + ) { + Person testPerson = new Person(true); + testPerson.setLes_c4_lag1(les_c4_lag1); + testPerson.setLes_c4(les_c4); + + return testPerson; + } + + @BeforeAll + public static void setupTestPopulation() { + + testPopulation = Arrays.asList( + // 25% move from employment into unemployment + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.NotEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.Student), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.NotEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.EmployedOrSelfEmployed, Les_c4.Retired), + // 50% from unemployment into employment + createTestPerson(Les_c4.NotEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.NotEmployed, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.NotEmployed, Les_c4.NotEmployed), + createTestPerson(Les_c4.NotEmployed, Les_c4.Retired), + // Ignore all rest as should be filtered out + createTestPerson(Les_c4.Student, Les_c4.Student), + createTestPerson(Les_c4.Retired, Les_c4.EmployedOrSelfEmployed), + createTestPerson(Les_c4.Retired, Les_c4.NotEmployed), + createTestPerson(Les_c4.Student, Les_c4.EmployedOrSelfEmployed) + ); + } + + @Test + @DisplayName("Proportion becoming unemployed") + public void proportionEmpToNotEmp() { + + + // Entering unemployment prevalence + EmploymentHistoryFilter employmentHistoryEmployed = new EmploymentHistoryFilter(Les_c4.EmployedOrSelfEmployed); + CrossSection.Integer personsEmpToNotEmp = new CrossSection.Integer(testPopulation, Person.class, "getNonwork", true); + personsEmpToNotEmp.setFilter(employmentHistoryEmployed); + + + MeanArrayFunction isEmpToNotEmp = new MeanArrayFunction(personsEmpToNotEmp); + isEmpToNotEmp.applyFunction(); + assertEquals(0.25, isEmpToNotEmp.getDoubleValue(IDoubleSource.Variables.Default)); + + } + + @Test + @DisplayName("Proportion becoming employed") + public void proportionNotEmpToEmp() { + + // Entering employment prevalence + EmploymentHistoryFilter employmentHistoryUnemployed = new EmploymentHistoryFilter(Les_c4.NotEmployed); + CrossSection.Integer personsNotEmpToEmp = new CrossSection.Integer(testPopulation, Person.class, "getEmployed", true); + personsNotEmpToEmp.setFilter(employmentHistoryUnemployed); + + MeanArrayFunction isNotEmpToEmp = new MeanArrayFunction(personsNotEmpToEmp); + isNotEmpToEmp.applyFunction(); + assertEquals(0.5, isNotEmpToEmp.getDoubleValue(IDoubleSource.Variables.Default)); + + + } + +}