From 1483438b41cd9e6a129963966f8b882106d33f96 Mon Sep 17 00:00:00 2001
From: Jake Tufts <137207796+JT-39@users.noreply.github.com>
Date: Tue, 26 Nov 2024 16:19:10 +0000
Subject: [PATCH] Create own inputs (#45)
* Fix: Re-adding input synchronisation across pages for LA
* Chore: Functionising the filtering of BDS by topic for app inputs, using raw BDS topic indicators to get dupes to appear across topics
* Chore: BDS metrics no longer needed in app inputs mod
* Feat: Converting Create Your Own inputs to not use topic to filter the BDS, in order to sort URL bookmarking
* Chore: Filtering out the discontinued indicators at the point the data dict is loaded in
* Feat: For duplicate indicators across topics, combining the topic names in BDS (mainly for Create Your Own table)
* Feat: Applying the decoipling of topic column to Create Own table (thus charts), had to update get x-axis fn to deal with multiple labels
* Feat: Functionising the dynamic topic label, though leaving out of Create Your Own for now as could have too many topics in label
* Chore: Roxgyen comments for dynamic topic label fn
* Feat: Updating sever script for new Create Your Own inputs param and removing the topic input from bookmarking
* Tests: Updating test fn to remove bds_metrics (no longer needed)
* Style: Linting indent corrections
* Fix: Topic filtering for dynamic label needs an any() wrapped around the check if topic input is all or empty
---
01_data/02_prod/LAIT Data Dictionary.csv | 39 --
02_dev/all_la_page/all_la_dev_app_mod.R | 1 -
02_dev/bds_wide_to_long.qmd | 351 ++++++++--------
.../create_own_table_dev_mod_app.R | 5 +-
02_dev/la_level_page/la_dev_app_mod.R | 1 -
R/fn_analysis.R | 17 +
R/fn_helper_functions.R | 67 +++
R/fn_plotting.R | 2 +-
R/lait_modules/mod_app_inputs.R | 383 ++++++++----------
R/lait_modules/mod_create_own_inputs.R | 47 +--
R/lait_modules/mod_create_own_table.R | 41 +-
global.R | 50 ++-
server.R | 10 +-
tests/testthat/test-UI-app_inputs.R | 2 +-
.../testthat/test-app_mod_la_lvl_table/app.R | 1 -
15 files changed, 527 insertions(+), 490 deletions(-)
diff --git a/01_data/02_prod/LAIT Data Dictionary.csv b/01_data/02_prod/LAIT Data Dictionary.csv
index 731aafd..67f4cc2 100644
--- a/01_data/02_prod/LAIT Data Dictionary.csv
+++ b/01_data/02_prod/LAIT Data Dictionary.csv
@@ -532,23 +532,6 @@ Number and percentage of children having a good level of development: This is a
Number and percentage at emerging level in early learning goal or area of learning: This is a count and percentage of children who were at the emerging level for a specific ELG or area of learning.
-Number and percentage at expected level in early learning goal or area of learning: This is a count and percentage of children who were at the expected level for a specific ELG or area of learning.","https://explore-education-statistics.service.gov.uk/find-statistics/early-years-foundation-stage-profile-results",NA,"Academic Year",NA
-"272","Foundation Stage","Foundation Stage - Inequality gap","Inequality gap in achievement across all the Early Learning Goals (%)","FSP_EQUGAP","Low","% gap","State Funded","DISCONTINUED","The Early Years Foundation Stage Profile (EYFSP) is a teacher assessment of children’s development at the end of the EYFS (the end of the academic year in which the child turns five). It should support a smooth transition to Key Stage 1 (KS1) by informing the professional dialogue between EYFS and KS1 teachers. This information should help Year 1 teachers plan an effective, responsive and appropriate curriculum that will meet the needs of all children. The Profile is also designed to inform parents or carers about their child’s development against the early learning goals.The statutory EYFS framework (opens in a new tab) sets the standards and requirements that all early years providers must follow to ensure all children have the best start in life and are prepared for school. It requires that children be assessed against the EYFS Profile in the summer term of the academic year in which they turn 5.
-
-The EYFS Profile is intended to provide an accurate representation of each child’s development at the end of the EYFS to support their transition into year 1. It is made up of an assessment of the child’s outcomes in relation to 17 early learning goals (ELGs) across 7 areas of learning.
-
-The 3 prime areas of learning are: communication and language; personal, social and emotional development; and physical development. These prime areas are particularly important for children’s healthy development and are the basis for successful learning in the other 4 specific areas of learning: literacy; mathematics; understanding the world; and expressive arts and design.",2023-11-01,"45597","DfE","Average number of early learning goals (ELGs) at expected level per child:
-
-This is the mean number of early learning goals children were at the expected level for. The maximum number would be 17, if every child was at the expected level for every early learning goal. This measure replaces the average point score measure from previous years.
-
-Number and percentage of children at expected level in all ELGs: This is a count and percentage of children who were at the expected level for all 17 ELGs.
-
-Number and percentage of children at expected level in all Communication and language and Literacy ELGs: This is a count and percentage of children who were at the expected level for the ELGs in the communication and language area of learning and the ELGs in the literacy area of learning.
-
-Number and percentage of children having a good level of development: This is a count and percentage of children who were at the expected level for all 12 ELGs within the 5 areas of learning relating to: communication and language; personal, social and emotional development; physical development; literacy; and mathematics.
-
-Number and percentage at emerging level in early learning goal or area of learning: This is a count and percentage of children who were at the emerging level for a specific ELG or area of learning.
-
Number and percentage at expected level in early learning goal or area of learning: This is a count and percentage of children who were at the expected level for a specific ELG or area of learning.","https://explore-education-statistics.service.gov.uk/find-statistics/early-years-foundation-stage-profile-results",NA,"Academic Year",NA
"272","Foundation Stage","Foundation Stage - Communication & Language","Children achieving at least the expected level in EYFS - Communication and Language (%)","FSP_NEW_COMMS","High","% achieving expected level","State Funded","2_eyfsp_early_learning_goals_areas_of_learning_2022_2023.csv","The Early Years Foundation Stage Profile (EYFSP) is a teacher assessment of children’s development at the end of the EYFS (the end of the academic year in which the child turns five). It should support a smooth transition to Key Stage 1 (KS1) by informing the professional dialogue between EYFS and KS1 teachers. This information should help Year 1 teachers plan an effective, responsive and appropriate curriculum that will meet the needs of all children. The Profile is also designed to inform parents or carers about their child’s development against the early learning goals.The statutory EYFS framework (opens in a new tab) sets the standards and requirements that all early years providers must follow to ensure all children have the best start in life and are prepared for school. It requires that children be assessed against the EYFS Profile in the summer term of the academic year in which they turn 5.
@@ -729,13 +712,6 @@ The EYFS Profile is intended to provide an accurate representation of each child
The 3 prime areas of learning are: communication and language; personal, social and emotional development; and physical development. These prime areas are particularly important for children’s healthy development and are the basis for successful learning in the other 4 specific areas of learning: literacy; mathematics; understanding the world; and expressive arts and design.",2023-11-01,"45597","DfE","Number and percentage of children having a good level of development: This is a count and percentage of children who were at the expected level for all 12 ELGs within the 5 areas of learning relating to: communication and language; personal, social and emotional development; physical development; literacy; and mathematics.
","https://explore-education-statistics.service.gov.uk/find-statistics/early-years-foundation-stage-profile-results",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Reading Expected Standard","Pupils achieving Key Stage 1 Reading Expected Standard (%)","KS1R_Expected_STD","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Reading Greater Depth","Pupils achieving Key Stage 1 Reading Greater Depth (%)","KS1R_Greater_Depth","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Writing Expected Standard","Pupils achieving Key Stage 1 Writing Expected Standard (%)","KS1W_Expected _STD","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Writing Greater Depth","Pupils achieving Key Stage 1 Writing Greater Depth (%)","KS1W_Greater_Depth","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Maths Expected Standard","Pupils achieving Key Stage 1 Maths Expected Standard (%)","KS1M_Expected _STD","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Maths Greater Depth","Pupils achieving Key Stage 1 Maths Greater Depth (%)","KS1M_Greater_Depth","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
-"347","Key Stage 1 and Phonics","Key Stage 1 Science Expected Standard","Pupils achieving Key Stage 1 Science Expected Standard (%)","KS1S_Expected_STD","High","% achieving expected level","State Funded","DISCONTINUED","Teacher assessment judgments in English reading, English writing, mathematics and science are reported for each pupil at the end of key stage 1 (typically aged 7). Teacher assessments are based on a broad range of evidence from across the curriculum and knowledge of how a pupil has performed over time and in a variety of contexts. Pupils are required to take tests in English and mathematics at the end of key stage 1, however teacher assessments is the only data used in school performance accountability at the end of KS1. New key stage 1 assessments were introduced in 2016 to assess the new, more challenging national curriculum and the expected standard was raised. As a result, figures from 2016 onwards are not comparable to earlier years",2023-10-01,"Discontinued","DfE","The new expected standards were designed to be broadly similar but are not equivalent to an old level 2b. The performance descriptors, used by teachers in the standard setting process, were developed with an understanding of the performance of pupils working at level 2b. However, given the curricula differences, there is not a direct equivalence between the new expected standard and level 2b in previous years.","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-1-and-phonics-screening-check-attainment",NA,"Academic Year",NA
"242","Key Stage 1 and Phonics","Phonics Decoding - All pupils","Pupils achieving expected level in Phonics decoding - all pupils (%)","KS1P_all","High","% achieving expected level","State Funded","phonics_regional_and_local_authority_2012_to_2023_provisional.csv","Pupils take the phonics screening check at the end of year 1, typically aged 6. Pupils who do not meet the expected standard take the check again at the end of year 2, typically aged 7.
The phonics screening check is a statutory assessment for year 1 pupils (typically aged 6) that confirms whether they have met the expected standard in phonic decoding. All state-funded schools with a year 1 cohort must administer the check. Pupils who do not meet the standard in year 1 or were not checked, must take part in the check at the end of year 2 (typically aged 7). Teachers administer the check one-on-one with each pupil and record whether their response to each of the 40 words is correct. Each pupil is awarded a mark between 0 and 40.
@@ -826,11 +802,6 @@ Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pan
Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pandemic.",2024-09-01,"45627","DfE","See official data release for further details","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-2-attainment",NA,"Academic Year","N"
-"346","Key Stage 2","KS2 TA - Expected standard Reading - All Pupils (no longer statutory requirement)","Key Stage 2 teacher assessments - Percentage reaching the expected standard Reading - All Pupils (no longer statutory requirement)","KS2R_Teacher_Assess","High","% achieving expected level","State Funded","DISCONTINUED","Key stage 2 - covers years 3 to 6 in primary schools. Pupils are normally 10 or 11 years old at the end of key stage 2.
-
-Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
-
-Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pandemic.",2024-09-01,"45627","DfE","See official data release for further details","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-2-attainment",NA,"Academic Year",NA
"346","Key Stage 2","KS2 - Expected standard Grammar, Punctuation & Spelling - All Pupils","Pupils meeting the expected standard in Grammar, Punctuation & Spelling (%)","KS2GPS_Expected_STD","High","% achieving expected level","State Funded","ks2_regional_local_authority_and_pupil_characteristics","Key stage 2 - covers years 3 to 6 in primary schools. Pupils are normally 10 or 11 years old at the end of key stage 2.
Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
@@ -861,11 +832,6 @@ Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pan
Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pandemic.",2024-09-01,"45627","DfE","See official data release for further details","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-2-attainment",NA,"Academic Year","N"
-"346","Key Stage 2","KS2 TA - expected standard Maths - All Pupils (no longer statutory requirement)","Key Stage 2 teacher assessments - Percentage reaching the expected standard Maths - All Pupils (no longer statutory requirement)","KS2M_Teacher_Assess","High","% achieving expected level","State Funded","DISCONTINUED","Key stage 2 - covers years 3 to 6 in primary schools. Pupils are normally 10 or 11 years old at the end of key stage 2.
-
-Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
-
-Assessments were cancelled in 2020 and 2021 due to the coronavirus (COVID19) pandemic.",2024-09-01,"45627","DfE","See official data release for further details","https://explore-education-statistics.service.gov.uk/find-statistics/key-stage-2-attainment",NA,"Academic Year",NA
"346","Key Stage 2","KS2 TA - expected standard Writing - All Pupils","Key Stage 2 teacher assessments - Pupils meeting the expected standard in writing (%)","KS2W_Teacher_Assess","High","% achieving expected level","State Funded","ks2_regional_local_authority_and_pupil_characteristics","Key stage 2 - covers years 3 to 6 in primary schools. Pupils are normally 10 or 11 years old at the end of key stage 2.
Statutory assessment for pupils in primary schools is the responsibility of the Standards and Testing Agency (STA). Once pupils have sat the KS2 tests, schools are required to send test materials and attendance registers to STA for external marking and data capture. Teacher assessments (TA) must also be submitted by schools to STA.
@@ -1591,7 +1557,6 @@ Awarding Organisation data including that collected as part of the School and Co
Pupil level Schools Census database containing information on the participation and personal characteristics of pupils in state schools, collected by DfE.
Awarding Organisation data including that collected as part of the School and Colleges Performance Tables exercise, and separately from awarding organisations as part of the Vocational Qualifications Database up until 2010/11. Individualised Learner Record (ILR) database covering participation and qualifications obtained in Further Education (FE) and Work-based Learning (WBL), collected by the FE Data Service from learning providers.","https://explore-education-statistics.service.gov.uk/find-statistics/level-2-and-3-attainment-by-young-people-aged-19",2,"Academic Year",NA
"190","Attainment by age 19","Inequality gap in Level 2 qualification","Inequality gap in the achievement of a Level 2 qualification by the age of 19 (%)","L2Age19Gap","Low","% gap",NA,"level_2_3_ages_16_19_local_authority_figures","This is the gap in the percentage attainment between individuals who were eligible and claiming free school meals at the age of 16 who turned 19 during the year and have passed the level 2 threshold, against those not eligible for FMS.",2024-04-01,"45748","DfE","The difference in attainment between the FSM eligible and not eligible group.","https://explore-education-statistics.service.gov.uk/find-statistics/level-2-and-3-attainment-by-young-people-aged-19",2,"Academic Year",NA
-"190a","Attainment by age 19","Inequality gap in Level 2 qualification inc E&M","Inequality gap in the achievement of a Level 2 qualification inc E&M by the age of 19 (%)","L2Age19_E&M_Gap","Low","% gap inc E&M",NA,"DISCONTINUED","This is the gap in the percentage attainment between individuals who were eligible and claiming free school meals at the age of 16 who turned 19 during the year and have passed the level 2 threshold, against those not eligible for FMS.",2020-04-01,"Discontinued","DfE","The difference in attainment between the FSM eligible and not eligible group.","https://explore-education-statistics.service.gov.uk/find-statistics/level-2-and-3-attainment-by-young-people-aged-19",2,"Academic Year",NA
"229","Attainment by age 19","Level 2 qualification in E&M by the age of 19 not achieved at 16","Level 2 in English and Maths at age 19 for those who had not achieved this level at 16 (%)","L2Age19_E&M_not16","High","% of 19 year olds",NA,"level_2_3_ages_16_19_local_authority_figures","Refers to the proportion of people who did not achieve level 2 in English and/or maths at 16 but had achieved both at 19 at 31st August i.e. the end of the academic year.
",2024-04-01,"45748","DfE","Several data sources are matched together at an individual level, using personal identifiers such as name, date of birth, gender and home postcode where available:
• Pupil level Schools Census database containing information on the participation and personal characteristics of pupils in state schools, collected by DfE.
@@ -1836,8 +1801,6 @@ Data on school stability is derived from matching the SSDA903 collection to the
Denominator: All children looked after at 31 March. Figures exclude children looked after under an agreed series of short term placements.
","https://explore-education-statistics.service.gov.uk/find-statistics/children-looked-after-in-england-including-adoptions",NA,"Financial Year",NA
-"341","Looked After Children","Number of approved foster places","Approved foster places (num)","Number_Foster_Places",NA,"Number of foster places",NA,"DISCONTINUED","Foster places refers to the total number of places that foster carers are approved to provide, whether occupied or not; it relates to the capacity of foster care in England (excludes IFAs)",2021-11-01,"Discontinued","Ofsted","See Ofsted data release for further details","https://www.gov.uk/government/collections/childrens-social-care-statistics",0,"Financial Year",NA
-"341","Looked After Children","Number of places that were filled (number of children placed)","Foster places that were filled: number of children placed (num)","Number_Foster_Places_Filled",NA,"Number of foster places filled",NA,"DISCONTINUED","Foster places refers to the total number of places that foster carers are approved to provide, whether occupied or not; it relates to the capacity of foster care in England (excludes IFAs)",2021-11-01,"Discontinued","Ofsted","See Ofsted data release for further details","https://www.gov.uk/government/collections/childrens-social-care-statistics",0,"Financial Year",NA
"341a","Looked After Children","Number of approved foster places (from 2022)","Approved foster places (excluding family and friends fostering) (num)","Number_Foster_Places(New)",NA,"Number of foster places",NA,"None standard - manual process","Foster places refers to the total number of places that foster carers are approved to provide, whether occupied or not; it relates to the capacity of foster care in England (excludes IFAs). From 2022, data excludes family and friends fostering",2024-11-01,"45962","Ofsted","See Ofsted data release for further details.
Hammersmith and Fulham, Kensington and Chelsea, and Westminster LAs submit a combined dataset under one URN as London Tri-borough.
@@ -3048,8 +3011,6 @@ These children are recorded as receiving free school meals on the school census,
£34,800 per annum for families within London with 2 or more children
These children are recorded as receiving free school meals on the school census, in the same way that children who are eligible for free school meals under the benefits-based criteria would be.",2024-06-01,"45809","DfE","-","https://www.gov.uk/government/collections/statistics-school-and-pupil-numbers",NA,"Annual - January",NA
-"303","Economic Factors","Primary Free School Meals 6","Primary pupils who have been eligible for free school meals at any point in the last 6 years","Ever6_FSM_Prim","Low","% of pupils",NA,"DISCONTINUE?","Deprivation Pupil Premium is allocated to those pupils on roll that are known to have been eligible for free school meals (FSM) on any pupil level census in the last six years (known as ‘Ever FSM 6’). FSM eligibility in the previous six years is determined by those pupils recorded on the latest January School/PRU and AP Census who were recorded as known to be eligible for FSM on any of the censuses (School, Pupil Referral Unit and Alternative Provision censuses) since the Summer 6 years previous.",2020-02-01,"TBC","DfE","FSM eligibility in the previous six years is determined by those pupils recorded on the January School/PRU and AP Census who were recorded as known to be eligible for FSM on any of the previous six years censuses.","-",NA,"Annual - January",NA
-"303","Economic Factors","Secondary Free School Meals 6","Secondary pupils who have been eligible for free school meals at any point in the last 6 years","Ever6_FSM_Sec","Low","% of pupils",NA,"DISCONTINUE?","Deprivation Pupil Premium is allocated to those pupils on roll that are known to have been eligible for free school meals (FSM) on any pupil level census in the last six years (known as ‘Ever FSM 6’). FSM eligibility in the previous six years is determined by those pupils recorded on the latest January School/PRU and AP Census who were recorded as known to be eligible for FSM on any of the censuses (School, Pupil Referral Unit and Alternative Provision censuses) since the Summer 6 years previous.",2020-02-01,"TBC","DfE","FSM eligibility in the previous six years is determined by those pupils recorded on the January School/PRU and AP Census who were recorded as known to be eligible for FSM on any of the previous six years censuses.","-",NA,"Annual - January",NA
"382","Economic Factors","16-17 NEET (from 2016)","16-17 year olds that are Not in Education, Employment or Training (%)","16-17 NEET","Low","% of 16 - 17 year olds",NA,"yyyy_NEET_and_participation_tables","Estimated proportion of 16 and 17 year-olds not in education, employment or training (NEET).
Participation estimates are based on data collected in March each year. In order to ensure the most robust estimates of NEET and not known rates an average of December/January/February data is used for an estimate around the end of the calendar year.",2023-07-01,"TBC","DfE","Data is collected using the National Client Caseload Information System (NCCIS), which draws together local databases used to support young people to engage in education and training and plan services that meet young people’s needs.
diff --git a/02_dev/all_la_page/all_la_dev_app_mod.R b/02_dev/all_la_page/all_la_dev_app_mod.R
index 1e79486..f56347b 100644
--- a/02_dev/all_la_page/all_la_dev_app_mod.R
+++ b/02_dev/all_la_page/all_la_dev_app_mod.R
@@ -46,7 +46,6 @@ server_mod <- function(input, output, session) {
app_inputs <- appInputsServer(
"all_la_inputs",
shared_values,
- bds_metrics,
topic_indicator_full
)
diff --git a/02_dev/bds_wide_to_long.qmd b/02_dev/bds_wide_to_long.qmd
index fccb208..183e1d1 100644
--- a/02_dev/bds_wide_to_long.qmd
+++ b/02_dev/bds_wide_to_long.qmd
@@ -1,175 +1,176 @@
----
-title: "LAIT - Shiny upgrade"
-subtitle: "Workings and development code"
-format: html
-date-modified: "`r Sys.Date()`"
-output:
- html_document:
- toc: true
-output-file: lait_workings
-embed-resources: true
-execute:
- echo: false
- cache: true
- message: false
- warning: false
----
-
-
-```{r src-funs}
-source(here::here("R/fn_helper_functions.R"))
-source(here::here("R/fn_load_data.R"))
-```
-
-Load the latest Data Dictionary
-
-```{r write-data_dict}
-# Load raw Data Dict from shared folder
-data_dict <- readxl::read_xlsx(
- path = paste0(shared_folder, "/../Information for App Development/LAIT Data Dictionary (To QA!).xlsx"),
- sheet = "Data_prod",
- # Replace multi-space with single-space
- .name_repair = clean_spaces
-)
-
-# Write .csv version
-write.csv(
- data_dict,
- here::here("01_data/02_prod/LAIT Data Dictionary.csv"),
- row.names = FALSE
-)
-```
-
-Logic to load BDS wide and convert to long
-
-```{r pull-latest-bds-data}
-# Load raw BDS wide from shared folder
-bds_wide_raw_sf <- readxl::read_xlsx(
- path = paste0(shared_folder, "/../Information for App Development/BDS - 2024 (JAKE).xlsx"),
- sheet = "BDS New",
- col_names = TRUE,
- skip = 1,
- # Replace multi-space with single-space
- .name_repair = clean_spaces,
- col_types = "text"
-)
-
-
-
-# Save to data folder
-write.csv(
- bds_wide_raw_sf,
- here::here("01_data/01_raw/BDS_Wide.csv"),
- row.names = FALSE
-)
-```
-
-
-```{r load-bds}
-# Load BDS wide
-bds_wide_raw <- read.csv(
- here::here("01_data/01_raw/BDS_Wide.csv"),
- check.names = FALSE
-)
-
-# styler: off
-# Remove any cols that are all na
-bds_wide <- bds_wide_raw |>
- { \(.) dplyr::select(., dplyr::all_of(non_empty_colnames(.))) }() # nolint: brace
-# styler: on
-```
-
-
-```{r wide-long}
-# Pivot BDS long on geographic cols
-bds_long <- bds_wide |>
- tidyr::pivot_longer(
- cols = `Barking and Dagenham`:`South West`,
- names_to = "LA and Regions",
- values_to = "Values"
- ) |>
- # Removes these lines with matching entries in year column
- dplyr::filter(
- Years %notin% c(
- "-",
- "",
- "Trend",
- "Change",
- "Rank",
- "Target",
- "Distance",
- "Quartile",
- "Deviation National",
- "% YoY change",
- "%3yr change",
- "2010 Target",
- "Target 2011",
- "NA - Distance",
- is.na(0)
- )
- )
-```
-
-
-```{r bds-clean}
-# Delete lines in vector from column, remove '.'s and underscores/number
-bds_long_clean <- bds_long |>
- dplyr::select(-line) |>
- dplyr::mutate(
- "LA and Regions" = stringr::str_replace_all(`LA and Regions`, "\\.", " "),
- "LA and Regions" = stringr::str_replace_all(`LA and Regions`, " ", "\\. "),
-
- # Remove final _ and following digits
- # NOTE: Also remove whitespace - 'LACStableNum_2016on'
- `Short Desc` = trimws(sub("_\\d+$", "", `Short Desc`))
- )
-```
-
-
-```{r bds-la_codes}
-# Load LA codes
-la_codes <- readxl::read_xlsx(
- path = here::here("01_data/02_prod/LA code list.xlsx")
-)
-
-# Join to bds and create combined codes var
-bds_long_la <- bds_long_clean |>
- dplyr::left_join(la_codes, by = c("LA and Regions" = "LA Name")) |>
- dplyr::select(-Type)
-```
-
-
-```{r bds-data_dict}
-# Pull list of measures from data dictionary (primary key and filters for
-# only public-safe/in-use measures)
-# NOTE: Children_away changed to Children_Away to match BDS
-data_dict <- read.csv(
- file = paste0("01_data/02_prod/LAIT Data Dictionary.csv"),
- check.names = FALSE
-) |>
- dplyr::mutate(Measure_short = trimws(Measure_short)) |>
- dplyr::filter(!grepl("DISCONTINUE", Table_status)) |>
- pull_uniques("Measure_short")
-
-# Filter BDS for only measures in the data dict
-bds_long_public <- bds_long_la |>
- dplyr::filter(`Short Desc` %in% data_dict)
-```
-
-
-```{r bds-save}
-# Write out parquet version
-arrow::write_dataset(
- bds_long_public,
- here::here("01_data/02_prod/"),
- format = "parquet",
- basename_template = "bds_long_{i}.parquet"
-)
-
-# Write .csv version (for public use)
-write.csv(
- bds_long_public,
- here::here("01_data/02_prod/bds_long.csv"),
- row.names = FALSE
-)
-```
+---
+title: "LAIT - Shiny upgrade"
+subtitle: "Workings and development code"
+format: html
+date-modified: "`r Sys.Date()`"
+output:
+ html_document:
+ toc: true
+output-file: lait_workings
+embed-resources: true
+execute:
+ echo: false
+ cache: true
+ message: false
+ warning: false
+---
+
+
+```{r src-funs}
+source(here::here("R/fn_helper_functions.R"))
+source(here::here("R/fn_load_data.R"))
+```
+
+Load the latest Data Dictionary
+
+```{r write-data_dict}
+# Load raw Data Dict from shared folder
+data_dict <- readxl::read_xlsx(
+ path = paste0(shared_folder, "/../Information for App Development/LAIT Data Dictionary (To QA!).xlsx"),
+ sheet = "Data_prod",
+ # Replace multi-space with single-space
+ .name_repair = clean_spaces
+) |>
+ dplyr::filter(!grepl("DISCONTINUE", Table_status))
+
+# Write .csv version
+write.csv(
+ data_dict,
+ here::here("01_data/02_prod/LAIT Data Dictionary.csv"),
+ row.names = FALSE
+)
+```
+
+Logic to load BDS wide and convert to long
+
+```{r pull-latest-bds-data}
+# Load raw BDS wide from shared folder
+bds_wide_raw_sf <- readxl::read_xlsx(
+ path = paste0(shared_folder, "/../Information for App Development/BDS - 2024 (JAKE).xlsx"),
+ sheet = "BDS New",
+ col_names = TRUE,
+ skip = 1,
+ # Replace multi-space with single-space
+ .name_repair = clean_spaces,
+ col_types = "text"
+)
+
+
+
+# Save to data folder
+write.csv(
+ bds_wide_raw_sf,
+ here::here("01_data/01_raw/BDS_Wide.csv"),
+ row.names = FALSE
+)
+```
+
+
+```{r load-bds}
+# Load BDS wide
+bds_wide_raw <- read.csv(
+ here::here("01_data/01_raw/BDS_Wide.csv"),
+ check.names = FALSE
+)
+
+# styler: off
+# Remove any cols that are all na
+bds_wide <- bds_wide_raw |>
+ { \(.) dplyr::select(., dplyr::all_of(non_empty_colnames(.))) }() # nolint: brace
+# styler: on
+```
+
+
+```{r wide-long}
+# Pivot BDS long on geographic cols
+bds_long <- bds_wide |>
+ tidyr::pivot_longer(
+ cols = `Barking and Dagenham`:`South West`,
+ names_to = "LA and Regions",
+ values_to = "Values"
+ ) |>
+ # Removes these lines with matching entries in year column
+ dplyr::filter(
+ Years %notin% c(
+ "-",
+ "",
+ "Trend",
+ "Change",
+ "Rank",
+ "Target",
+ "Distance",
+ "Quartile",
+ "Deviation National",
+ "% YoY change",
+ "%3yr change",
+ "2010 Target",
+ "Target 2011",
+ "NA - Distance",
+ is.na(0)
+ )
+ )
+```
+
+
+```{r bds-clean}
+# Delete lines in vector from column, remove '.'s and underscores/number
+bds_long_clean <- bds_long |>
+ dplyr::select(-line) |>
+ dplyr::mutate(
+ "LA and Regions" = stringr::str_replace_all(`LA and Regions`, "\\.", " "),
+ "LA and Regions" = stringr::str_replace_all(`LA and Regions`, " ", "\\. "),
+
+ # Remove final _ and following digits
+ # NOTE: Also remove whitespace - 'LACStableNum_2016on'
+ `Short Desc` = trimws(sub("_\\d+$", "", `Short Desc`))
+ )
+```
+
+
+```{r bds-la_codes}
+# Load LA codes
+la_codes <- readxl::read_xlsx(
+ path = here::here("01_data/02_prod/LA code list.xlsx")
+)
+
+# Join to bds and create combined codes var
+bds_long_la <- bds_long_clean |>
+ dplyr::left_join(la_codes, by = c("LA and Regions" = "LA Name")) |>
+ dplyr::select(-Type)
+```
+
+
+```{r bds-data_dict}
+# Pull list of measures from data dictionary (primary key and filters for
+# only public-safe/in-use measures)
+# NOTE: Children_away changed to Children_Away to match BDS
+data_dict <- read.csv(
+ file = paste0("01_data/02_prod/LAIT Data Dictionary.csv"),
+ check.names = FALSE
+) |>
+ dplyr::mutate(Measure_short = trimws(Measure_short)) |>
+ dplyr::filter(!grepl("DISCONTINUE", Table_status)) |>
+ pull_uniques("Measure_short")
+
+# Filter BDS for only measures in the data dict
+bds_long_public <- bds_long_la |>
+ dplyr::filter(`Short Desc` %in% data_dict)
+```
+
+
+```{r bds-save}
+# Write out parquet version
+arrow::write_dataset(
+ bds_long_public,
+ here::here("01_data/02_prod/"),
+ format = "parquet",
+ basename_template = "bds_long_{i}.parquet"
+)
+
+# Write .csv version (for public use)
+write.csv(
+ bds_long_public,
+ here::here("01_data/02_prod/bds_long.csv"),
+ row.names = FALSE
+)
+```
diff --git a/02_dev/create_your_own_page/create_own_table_dev_mod_app.R b/02_dev/create_your_own_page/create_own_table_dev_mod_app.R
index 36f3450..c1639a9 100644
--- a/02_dev/create_your_own_page/create_own_table_dev_mod_app.R
+++ b/02_dev/create_your_own_page/create_own_table_dev_mod_app.R
@@ -61,7 +61,10 @@ ui <- bslib::page_fillable(
# Main App Server
server <- function(input, output, session) {
# Call the main inputs module
- create_inputs <- Create_MainInputsServer("create_inputs", bds_metrics)
+ create_inputs <- Create_MainInputsServer(
+ "create_inputs",
+ topic_indicator_full
+ )
# Year range
year_input <- YearRangeServer(
diff --git a/02_dev/la_level_page/la_dev_app_mod.R b/02_dev/la_level_page/la_dev_app_mod.R
index 5460e5c..88f975c 100644
--- a/02_dev/la_level_page/la_dev_app_mod.R
+++ b/02_dev/la_level_page/la_dev_app_mod.R
@@ -66,7 +66,6 @@ server_mod <- function(input, output, session) {
app_inputs <- appInputsServer(
"la_inputs",
shared_values,
- bds_metrics,
metrics_raw
)
diff --git a/R/fn_analysis.R b/R/fn_analysis.R
index 5bada7e..6db2f57 100644
--- a/R/fn_analysis.R
+++ b/R/fn_analysis.R
@@ -429,3 +429,20 @@ get_query_table_values <- function(data, column) {
dplyr::mutate({{ column }} := trimws({{ column }})) |>
pull_uniques(as.character(substitute(column)))
}
+
+
+#' Filter data based on topic selection
+#'
+#' @param data A data frame or tibble to filter.
+#' @param topic_column The name of the column containing topic values (as a string).
+#' @param selected_topics A vector of selected topics from the user input.
+#' @return A filtered data frame or tibble based on the topic selection.
+filter_by_topic <- function(data, topic_column, selected_topics) {
+ # Check if selected topics are all selected or empty (return whole df if so)
+ if (is.null(selected_topics) || any(selected_topics %in% c("All Topics", ""))) {
+ return(data)
+ }
+
+ # Filter by selected topic
+ dplyr::filter(data, .data[[topic_column]] %in% selected_topics)
+}
diff --git a/R/fn_helper_functions.R b/R/fn_helper_functions.R
index bd7673b..c1b9d13 100644
--- a/R/fn_helper_functions.R
+++ b/R/fn_helper_functions.R
@@ -693,3 +693,70 @@ with_gov_spinner <- function(ui_element, spinner_type = 6, size = 1) {
proxy.height = paste0(250 * size, "px")
)
}
+
+
+#' Update Topic Label Dynamically
+#'
+#' Dynamically updates a topic label in the user interface based on the selected
+#' indicator and topic inputs. If no topic is selected or "All Topics" is
+#' chosen, the label reflects the related topic(s) of the selected indicator.
+#'
+#' @param indicator_input A reactive expression that returns the selected
+#' indicator input value.
+#' @param topic_input A reactive expression that returns the selected topic
+#' input value.
+#' @param topic_indicator_data A data frame containing `Topic` and `Measure`
+#' columns, used to identify related topics for a given indicator.
+#' @param topic_label_id A string specifying the ID of the HTML element where
+#' the topic label will be updated. Defaults to `"topic_label"`.
+#'
+#' @details
+#' This function listens for changes in the provided `indicator_input` and
+#' `topic_input`. If the topic is not selected or is set to "All Topics", the
+#' label is updated to include the topic(s) related to the selected indicator.
+#' Multiple topics are combined with a " / " separator.
+#'
+#' @return None. The function operates for its side effects of updating the UI.
+#'
+#' @examples
+#' # In a Shiny server function
+#' update_topic_label(
+#' indicator_input = reactive(input$indicator),
+#' topic_input = reactive(input$topic_input),
+#' topic_indicator_data = topic_indicator_full,
+#' topic_label_id = "topic_label"
+#' )
+#'
+update_topic_label <- function(
+ indicator_input,
+ topic_input,
+ topic_indicator_data,
+ topic_label_id = "topic_label") {
+ shiny::observeEvent(c(indicator_input(), topic_input()), {
+ indicator <- indicator_input()
+ topic <- topic_input()
+
+ # Default topic label
+ label <- "Topic:"
+
+ # Update label if conditions are met
+ if (!is.null(indicator) && indicator != "" && (topic %in% c("", "All Topics"))) {
+ # Get related topic(s)
+ related_topics <- topic_indicator_data |>
+ dplyr::filter(.data$Measure == indicator) |>
+ pull_uniques("Topic")
+
+ # Create label, combining topics if multiple exist
+ if (length(related_topics) > 0) {
+ label <- paste0(
+ "Topic: (",
+ paste0(related_topics, collapse = " / "),
+ ")"
+ )
+ }
+ }
+
+ # Update the HTML element with the new label
+ shinyjs::html(topic_label_id, label)
+ })
+}
diff --git a/R/fn_plotting.R b/R/fn_plotting.R
index d6c89d2..4617f39 100644
--- a/R/fn_plotting.R
+++ b/R/fn_plotting.R
@@ -76,7 +76,7 @@ get_xaxis_title <- function(data_full) {
pull_uniques("Year_Type")
# If more than one y-axis title then give generic
- if (is.na(x_axis_title)) {
+ if (length(x_axis_title) == 0 || all(is.na(x_axis_title))) {
"Years (no type given)"
} else if (length(x_axis_title) == 1) {
add_line_breaks(x_axis_title)
diff --git a/R/lait_modules/mod_app_inputs.R b/R/lait_modules/mod_app_inputs.R
index 107958a..7689286 100644
--- a/R/lait_modules/mod_app_inputs.R
+++ b/R/lait_modules/mod_app_inputs.R
@@ -1,204 +1,179 @@
-# nolint start: object_name
-#
-#' Shiny Module UI for Displaying the App Inputs
-#'
-#' This function creates a Shiny UI module for displaying the app inputs.
-#' The inputs include a select input for the local authority (LA) name,
-#' the topic name, and the indicator name.
-#' Each input is wrapped in a div with a well and a layout column for styling.
-#'
-#' @param id A unique ID that identifies the UI element
-#' @return A div object that contains the UI elements for the module
-#'
-appInputsUI <- function(id) {
- ns <- NS(id)
-
- div(
- class = "well",
- style = "overflow-y: visible; position: relative;",
- bslib::layout_column_wrap(
- width = "15rem", # Minimum width for each input box before wrapping
- shiny::selectizeInput(
- inputId = ns("la_name"),
- label = "Local Authority:",
- choices = la_names_bds,
- options = list(
- placeholder = "Select a Local Authority...",
- plugins = list("clear_button")
- )
- ),
- shiny::selectizeInput(
- inputId = ns("topic_name"),
- label = tags$label(
- id = ns("topic_label"),
- "Topic:"
- ),
- choices = c("All Topics", metric_topics),
- selected = "All Topics",
- options = list(
- placeholder = "No topic selected, showing all indicators.",
- plugins = list("clear_button")
- )
- ),
- shiny::selectizeInput(
- inputId = ns("indicator_name"),
- label = "Indicator:",
- choices = metric_names,
- options = list(
- placeholder = "Select an indicator...",
- plugins = list("clear_button")
- )
- )
- )
- )
-}
-
-
-#' Shiny Server Function for Handling the App Inputs with Synchronisation
-#'
-#' This function creates a Shiny server module for handling the app inputs
-#' and synchronising them across multiple pages.
-#' It observes the selected topic name and updates the choices for the
-#' indicator name based on the selected topic, and also updates the shared
-#' reactive values to keep the inputs in sync between pages.
-#'
-#' @param id A unique ID that identifies the server function.
-#' @param shared_values A `reactiveValues` object to store shared input values
-#' that can be accessed and modified across different modules.
-#' @return A list of reactive expressions for the app inputs, including
-#' the selected LA name, topic name, and indicator name.
-#'
-appInputsServer <- function(id,
- shared_values,
- bds_metrics,
- topic_indicator_full) {
- moduleServer(id, function(input, output, session) {
- # Reactive value to store the previous LA name
- previous_la_name <- reactiveVal(NULL)
-
- # Debounce input values to prevent looping when inputs change quickly
- debounced_la_name <- shiny::debounce(reactive(input$la_name), 150)
- debounced_topic_name <- shiny::debounce(reactive(input$topic_name), 150)
- debounced_indicator_name <- shiny::debounce(reactive(input$indicator_name), 150)
-
- # Observe and synchronise LA input across pages
- observe({
- # Check if the LA name is NULL or empty
- la_name <- debounced_la_name()
-
- if ("" %notin% la_name && !is.null(la_name)) {
- # Update the reactive value with the current valid input
- previous_la_name(la_name)
-
- # Synchronise the shared reactive value
- shared_values$la <- la_name
- }
- })
-
- observe({
- shiny::updateSelectizeInput(session, "topic_name", selected = shared_values$topic)
- })
-
- observe({
- shiny::updateSelectizeInput(session, "indicator_name", selected = shared_values$indicator)
- })
-
- # Update Indicator dropdown for selected Topic
- shiny::observeEvent(debounced_topic_name(),
- {
- # Save the currently selected indicator
- current_indicator <- debounced_indicator_name()
-
- # Determine the filter condition for Topic
- topic_filter <- debounced_topic_name()
-
- # Get indicator choices for selected topic
- # Include all rows if no topic is selected or "All Topics" is selected
- filtered_topic_bds <- bds_metrics |>
- dplyr::filter(
- if (is.null(input$topic_name) ||
- "All Topics" %in% input$topic_name ||
- "" %in% input$topic_name) {
- TRUE
- } else {
- .data$Topic %in% input$topic_name # Filter by selected topic(s)
- }
- ) |>
- pull_uniques("Measure")
-
- # Ensure the current indicator stays selected
- # Default to the first topic indicator if the current is not valid
- selected_indicator <- if (current_indicator %in% filtered_topic_bds) {
- current_indicator
- } else {
- filtered_topic_bds[1]
- }
-
- # Update the Indicator dropdown based on selected Topic
- shiny::updateSelectizeInput(
- session = session,
- inputId = "indicator_name",
- choices = filtered_topic_bds,
- selected = selected_indicator
- )
-
- # Update the shared reactive value for the topic
- shared_values$topic <- debounced_topic_name()
- },
- ignoreNULL = FALSE
- )
-
- # Default topic label
- topic_label <- "Topic:"
-
- # Dynamically update the Topic label with the related topic
- shiny::observeEvent(c(debounced_indicator_name(), debounced_topic_name()), {
- indicator <- debounced_indicator_name()
- topic <- debounced_topic_name()
-
- # When no topic is selected, show the currently selected indicator topic
- # in the topic label
- if (!is.null(indicator) && indicator != "" &&
- (topic %in% c("", "All Topics"))) {
- # Get topic
- related_topic <- topic_indicator_full |>
- dplyr::filter(.data$Measure == indicator) |>
- pull_uniques("Topic")
-
- # Create label, combine topics if multiple exist
- if (length(related_topic) > 0) {
- topic_label <- paste0(
- "Topic: (",
- paste0(related_topic, collapse = ", "),
- ")"
- )
- }
- }
-
- # Apply topic label
- shinyjs::html("topic_label", topic_label)
- })
-
- # Observe and synchronise Indicator input changes
- observeEvent(debounced_indicator_name(), {
- shared_values$indicator <- debounced_indicator_name()
- })
-
- # Return reactive settings
- app_settings <- list(
- la = reactive({
- previous_la_name()
- }),
- topic = reactive({
- debounced_topic_name()
- }),
- indicator = reactive({
- debounced_indicator_name()
- })
- )
-
- return(app_settings)
- })
-}
-
-# nolint end
+# nolint start: object_name
+#
+#' Shiny Module UI for Displaying the App Inputs
+#'
+#' This function creates a Shiny UI module for displaying the app inputs.
+#' The inputs include a select input for the local authority (LA) name,
+#' the topic name, and the indicator name.
+#' Each input is wrapped in a div with a well and a layout column for styling.
+#'
+#' @param id A unique ID that identifies the UI element
+#' @return A div object that contains the UI elements for the module
+#'
+appInputsUI <- function(id) {
+ ns <- NS(id)
+
+ div(
+ class = "well",
+ style = "overflow-y: visible; position: relative;",
+ bslib::layout_column_wrap(
+ width = "15rem", # Minimum width for each input box before wrapping
+ shiny::selectizeInput(
+ inputId = ns("la_name"),
+ label = "Local Authority:",
+ choices = la_names_bds,
+ options = list(
+ placeholder = "Select a Local Authority...",
+ plugins = list("clear_button")
+ )
+ ),
+ shiny::selectizeInput(
+ inputId = ns("topic_name"),
+ label = tags$label(
+ id = ns("topic_label"),
+ "Topic:"
+ ),
+ choices = c("All Topics", metric_topics),
+ selected = "All Topics",
+ options = list(
+ placeholder = "No topic selected, showing all indicators.",
+ plugins = list("clear_button")
+ )
+ ),
+ shiny::selectizeInput(
+ inputId = ns("indicator_name"),
+ label = "Indicator:",
+ choices = metric_names,
+ options = list(
+ placeholder = "Select an indicator...",
+ plugins = list("clear_button")
+ )
+ )
+ )
+ )
+}
+
+
+#' Shiny Server Function for Handling the App Inputs with Synchronisation
+#'
+#' This function creates a Shiny server module for handling the app inputs
+#' and synchronising them across multiple pages.
+#' It observes the selected topic name and updates the choices for the
+#' indicator name based on the selected topic, and also updates the shared
+#' reactive values to keep the inputs in sync between pages.
+#'
+#' @param id A unique ID that identifies the server function.
+#' @param shared_values A `reactiveValues` object to store shared input values
+#' that can be accessed and modified across different modules.
+#' @return A list of reactive expressions for the app inputs, including
+#' the selected LA name, topic name, and indicator name.
+#'
+appInputsServer <- function(id,
+ shared_values,
+ topic_indicator_full) {
+ moduleServer(id, function(input, output, session) {
+ # Reactive value to store the previous LA name
+ previous_la_name <- reactiveVal(NULL)
+
+ # Debounce input values to prevent looping when inputs change quickly
+ debounced_la_name <- shiny::debounce(reactive(input$la_name), 150)
+ debounced_topic_name <- shiny::debounce(reactive(input$topic_name), 150)
+ debounced_indicator_name <- shiny::debounce(reactive(input$indicator_name), 150)
+
+ # Synchronise inputs across pages:
+ # LA
+ observe({
+ shiny::updateSelectizeInput(session, "la_name", selected = shared_values$la)
+ })
+ # Topic
+ observe({
+ shiny::updateSelectizeInput(session, "topic_name", selected = shared_values$topic)
+ })
+ # Indicator
+ observe({
+ shiny::updateSelectizeInput(session, "indicator_name", selected = shared_values$indicator)
+ })
+
+ # Update Indicator dropdown for selected Topic
+ shiny::observeEvent(debounced_topic_name(),
+ {
+ # Save the currently selected indicator
+ current_indicator <- debounced_indicator_name()
+
+ # Determine the filter condition for Topic
+ topic_filter <- debounced_topic_name()
+
+ # Get indicator choices for selected topic
+ # Include all rows if no topic is selected or "All Topics" is selected
+ filtered_topic_bds <- topic_indicator_full |>
+ filter_by_topic("Topic", topic_filter) |>
+ pull_uniques("Measure")
+
+ # Ensure the current indicator stays selected
+ # Default to the first topic indicator if the current is not valid
+ selected_indicator <- if (current_indicator %in% filtered_topic_bds) {
+ current_indicator
+ } else {
+ filtered_topic_bds[1]
+ }
+
+ # Update the Indicator dropdown based on selected Topic
+ shiny::updateSelectizeInput(
+ session = session,
+ inputId = "indicator_name",
+ choices = filtered_topic_bds,
+ selected = selected_indicator
+ )
+
+ # Update the shared reactive value for the topic
+ shared_values$topic <- debounced_topic_name()
+ },
+ ignoreNULL = FALSE
+ )
+
+ # Prevent LA input from being empty by storing its previous value
+ shiny::observeEvent(debounced_la_name(), {
+ # Check if the LA name is NULL or empty
+ la_name <- debounced_la_name()
+
+ if ("" %notin% la_name && !is.null(la_name)) {
+ # Update the reactive value with the current valid input
+ previous_la_name(la_name)
+
+ # Synchronise the shared reactive value
+ shared_values$la <- la_name
+ }
+ })
+
+ # Set dynamic topic label
+ # (to display topic when not selected or all topics selected)
+ update_topic_label(
+ indicator_input = debounced_indicator_name,
+ topic_input = debounced_topic_name,
+ topic_indicator_data = topic_indicator_full,
+ topic_label_id = "topic_label"
+ )
+
+ # Observe and synchronise Indicator input changes
+ observeEvent(debounced_indicator_name(), {
+ shared_values$indicator <- debounced_indicator_name()
+ })
+
+ # Return reactive settings
+ app_settings <- list(
+ la = reactive({
+ previous_la_name()
+ }),
+ topic = reactive({
+ debounced_topic_name()
+ }),
+ indicator = reactive({
+ debounced_indicator_name()
+ })
+ )
+
+ return(app_settings)
+ })
+}
+
+# nolint end
diff --git a/R/lait_modules/mod_create_own_inputs.R b/R/lait_modules/mod_create_own_inputs.R
index 5b80aaa..4f9b6ee 100644
--- a/R/lait_modules/mod_create_own_inputs.R
+++ b/R/lait_modules/mod_create_own_inputs.R
@@ -29,7 +29,7 @@ Create_MainInputsUI <- function(id) {
choices = c(la_names_bds, region_names_bds, "England"),
multiple = TRUE,
options = list(
- "placeholder" = "Select a LA, Region or England",
+ "placeholder" = "Select a LA, Region or England...",
plugins = list("remove_button")
)
)
@@ -40,7 +40,12 @@ Create_MainInputsUI <- function(id) {
shiny::selectizeInput(
inputId = ns("topic_input"),
label = "Topic:",
- choices = metric_topics
+ choices = c("All Topics", metric_topics),
+ selected = "All Topics",
+ options = list(
+ placeholder = "No topic selected, showing all indicators.",
+ plugins = list("clear_button")
+ )
)
),
# Indicator input
@@ -52,7 +57,7 @@ Create_MainInputsUI <- function(id) {
choices = metric_names,
multiple = TRUE,
options = list(
- "placeholder" = "Select an indicator",
+ "placeholder" = "Select an indicator...",
plugins = list("remove_button")
)
)
@@ -113,40 +118,35 @@ Create_MainInputsUI <- function(id) {
#' used for filtering indicators based on selected topics.
#' @return A list of reactive values containing user inputs.
#'
-Create_MainInputsServer <- function(id, bds_metrics) {
+Create_MainInputsServer <- function(id, topic_indicator_full) {
moduleServer(id, function(input, output, session) {
# Reactive to store all selected topic-indicator pairs
# Used to filter BDS correctly (due to duplication of indicator names
# across topics)
- selected_indicators <- reactiveVal({
- data.frame(
- Topic = character(),
- Measure = character()
- )
- })
+ selected_indicators <- reactiveVal(NULL)
# Filter indicator choices based on the selected topic
# But keep already selected indicators from other topics
shiny::observeEvent(input$topic_input, {
# Available indicators (based on topic chosen)
- filtered_topic_bds <- bds_metrics |>
- dplyr::filter(Topic %in% input$topic_input) |>
- dplyr::select(Topic, Measure)
+ topic_indicators <- topic_indicator_full |>
+ filter_by_topic("Topic", input$topic_input) |>
+ pull_uniques("Measure")
# Get the already selected topic-indicator pairs
current_selection <- selected_indicators()
# Combine already selected topic-indicator pairs with new topic indicators
# Allows indicators to stay selected despite not being part of the new topic
- combined_choices <- unique(rbind(current_selection, filtered_topic_bds))
+ combined_choices <- unique(c(current_selection, topic_indicators))
# Update the choices with new topic whilst retaining the
# already selected indicators
shiny::updateSelectizeInput(
session = session,
inputId = "indicator",
- choices = combined_choices$Measure,
- selected = current_selection$Measure
+ choices = combined_choices,
+ selected = current_selection
)
})
@@ -155,23 +155,20 @@ Create_MainInputsServer <- function(id, bds_metrics) {
shiny::observeEvent(input$indicator,
{
# Get the new topic-indicator pairs
- current_filtered <- bds_metrics |>
+ current_filtered <- topic_indicator_full |>
dplyr::filter(
- Topic %in% input$topic_input,
Measure %in% input$indicator
) |>
- dplyr::distinct(Topic, Measure)
+ pull_uniques("Measure")
# Get previously selected indicators
previous_selection <- selected_indicators()
# Remove any topic-indicator pairs that have been deselected
- deselected_measures <- setdiff(previous_selection$Measure, input$indicator)
- updated_selection <- previous_selection |>
- dplyr::filter(!Measure %in% deselected_measures)
+ updated_selection <- setdiff(input$indicator, previous_selection)
# Combine the new topic-indicator pairs with the previous selections
- combined_selection <- unique(rbind(updated_selection, current_filtered))
+ combined_selection <- unique(c(updated_selection, current_filtered))
# Update the reactive value for all topic-indicator pairs
selected_indicators(combined_selection)
@@ -195,9 +192,7 @@ Create_MainInputsServer <- function(id, bds_metrics) {
# Return create your own main inputs
create_inputs <- list(
geog = reactive(input$geog_input),
- topic = reactive(selected_indicators()$Topic),
- indicator = reactive(selected_indicators()$Measure),
- selected_indicators = reactive(selected_indicators()),
+ indicator = reactive(selected_indicators()),
la_group = reactive(input$la_group),
inc_regions = reactive(input$inc_regions),
inc_england = reactive(input$inc_england),
diff --git a/R/lait_modules/mod_create_own_table.R b/R/lait_modules/mod_create_own_table.R
index da5de6f..d57e4e4 100644
--- a/R/lait_modules/mod_create_own_table.R
+++ b/R/lait_modules/mod_create_own_table.R
@@ -36,15 +36,9 @@ StagingBDSServer <- function(id,
# Filter BDS for topic-indicator pairs in the selected_values reactive
topic_indicator_bds <- reactive({
- req(nrow(create_inputs$selected_indicators()) > 0)
+ req(length(create_inputs$indicator()) > 0)
bds_metrics |>
- dplyr::semi_join(
- create_inputs$selected_indicators(),
- by = c(
- "Topic" = "Topic",
- "Measure" = "Measure"
- )
- )
+ dplyr::filter(Measure %in% create_inputs$indicator())
})
# Now filter BDS for geographies and year range
@@ -379,7 +373,7 @@ QueryDataServer <- function(id,
observeEvent(create_inputs$add_query(),
{
# Check if anything selected
- req(length(geog_groups()) > 0 && nrow(create_inputs$selected_indicators()) > 0)
+ req(length(geog_groups()) > 0 && length(create_inputs$indicator()) > 0)
# Create a unique identifier for the new query (current no of queries + 1)
new_q_id <- max(c(0, query$data$.query_id), na.rm = TRUE) + 1
@@ -406,6 +400,10 @@ QueryDataServer <- function(id,
inc_england = create_inputs$inc_england()
)
+ # Get selected Indicator Topics
+ selected_topics <- staging_data() |>
+ pull_uniques("Topic")
+
# Create query information
# Split multiple input choices with commas and line breaks
# (indicator x, indicator y)
@@ -414,8 +412,8 @@ QueryDataServer <- function(id,
# year range (with logic from above) and the remove col
new_query <- data.frame(
.query_id = new_q_id,
- Topic = I(list(create_inputs$selected_indicators()$Topic)),
- Indicator = paste(create_inputs$selected_indicators()$Measure, collapse = ",
"),
+ Topic = paste(selected_topics, collapse = ",
"),
+ Indicator = paste(create_inputs$indicator(), collapse = ",
"),
`LA and Regions` = paste(
get_geog_selection(evaluated_inputs, la_names_bds, region_names_bds, stat_n_geog),
collapse = ",
"
@@ -559,17 +557,7 @@ QueryTableServer <- function(id, query) {
}"
)
),
- Topic = reactable::colDef(
- cell = function(value) {
- unique_values <- unique(unlist(value))
- if (length(unique_values) > 0) {
- return(paste(unique_values, collapse = ",
"))
- } else {
- return("")
- }
- },
- html = TRUE
- ),
+ Topic = html_col_def(),
.query_id = reactable::colDef(show = FALSE)
),
defaultPageSize = 5,
@@ -602,7 +590,14 @@ QueryTableServer <- function(id, query) {
# the legacy years aren't still there
if (nrow(query$output) == 0) {
query$output <- query$output |>
- dplyr::select(`LA Number`, `LA and Regions`, Region, Topic, Measure)
+ dplyr::select(
+ `LA Number`,
+ `LA and Regions`,
+ Region,
+ Topic,
+ Measure,
+ .query_id
+ )
}
},
ignoreInit = TRUE
diff --git a/global.R b/global.R
index 2611c14..6792010 100644
--- a/global.R
+++ b/global.R
@@ -153,10 +153,32 @@ stat_n_geog <- stat_n |>
# Metrics
-# Remove whitesapce from key & filter out discontinued metrics
+# Filter out discontinued metrics
+metrics_included <- metrics_raw |>
+ dplyr::filter(!grepl("DISCONTINUE", Table_status))
+
+# Topic and indicators pairs (full - no duplicates filtered out)
+topic_indicator_full <- metrics_included |>
+ dplyr::distinct(Topic, Measure)
+
+# Duplicate indicators across topics
+dupes_across_topics <- topic_indicator_full |>
+ dplyr::filter(dplyr::n() > 1, .by = "Measure")
+
+# For each dupe combine topic names
+dupes_combined_topics <- dupes_across_topics |>
+ dplyr::summarise(
+ Topic = stringr::str_c(unique(Topic), collapse = " / "),
+ .by = "Measure"
+ )
+
+# Cleaning
+# Remove whitesapce from key
# Set any NA decimal place column values to 1
# Convert Last and Next updated to Format Month Year
-metrics_clean <- metrics_raw |>
+# Add in combined topic names for duplicate indicators
+# Remove duplicates
+metrics_clean <- metrics_included |>
dplyr::mutate(
Measure_short = trimws(Measure_short),
dps = ifelse(is.na(dps), 1, dps),
@@ -177,15 +199,25 @@ metrics_clean <- metrics_raw |>
TRUE ~ as.character(`Next Update`)
)
) |>
- dplyr::filter(!grepl("DISCONTINUE", Table_status)) |>
- # Removing any second instances of a Measure (duplicate across Topics)
+ dplyr::left_join(
+ dupes_combined_topics,
+ by = "Measure",
+ suffix = c("", "_dupe_combined")
+ ) |>
+ # Update Topic where combined values exist
+ dplyr::mutate(
+ Topic = dplyr::case_when(
+ !is.na(Topic_dupe_combined) ~ Topic_dupe_combined,
+ TRUE ~ Topic
+ )
+ ) |>
+ dplyr::select(-Topic_dupe_combined) |>
dplyr::filter(!duplicated(Measure))
metrics_discontinued <- metrics_raw |>
dplyr::filter(Measure_short %notin% metrics_clean$Measure_short) |>
pull_uniques("Measure_short")
-
# Joining data ================================================================
# BDS & metrics (left join as have cleaned metrics for discontinued)
@@ -356,15 +388,11 @@ testthat::test_that("Ther are 11 Region names & match Stat Neighbours", {
)
})
-# Topic and indicators pairs (full - no duplicates filtered out)
-topic_indicator_full <- metrics_raw |>
- dplyr::distinct(Topic, Measure)
-
# Metric topics
-metric_topics <- pull_uniques(metrics_clean, "Topic")
+metric_topics <- pull_uniques(topic_indicator_full, "Topic")
# Metric names
-metric_names <- pull_uniques(metrics_clean, "Measure")
+metric_names <- pull_uniques(topic_indicator_full, "Measure")
# Indicators that are impacted by COVID
# (aka missing data across all LAs for a whole year between 2091-2022)
diff --git a/server.R b/server.R
index 15cc21b..03d2fb1 100644
--- a/server.R
+++ b/server.R
@@ -29,7 +29,6 @@ server <- function(input, output, session) {
"la_inputs-indicator_name",
"navsetpillslist",
"create_inputs-geog_input",
- "create_inputs-topic_input",
"create_inputs-indicator",
"create_inputs-la_group",
"create_inputs-inc_regions",
@@ -116,7 +115,6 @@ server <- function(input, output, session) {
la_app_inputs <- appInputsServer(
"la_inputs",
shared_page_inputs,
- bds_metrics,
topic_indicator_full
)
@@ -182,7 +180,6 @@ server <- function(input, output, session) {
region_app_inputs <- appInputsServer(
"region_inputs",
shared_page_inputs,
- bds_metrics,
topic_indicator_full
)
@@ -279,7 +276,6 @@ server <- function(input, output, session) {
stat_n_app_inputs <- appInputsServer(
"stat_n_inputs",
shared_page_inputs,
- bds_metrics,
topic_indicator_full
)
@@ -372,7 +368,6 @@ server <- function(input, output, session) {
all_la_app_inputs <- appInputsServer(
"all_la_inputs",
shared_page_inputs,
- bds_metrics,
topic_indicator_full
)
@@ -408,7 +403,10 @@ server <- function(input, output, session) {
# ===========================================================================
# User Inputs ===============================================================
# Create own main inputs ----------------------------------------------------
- create_inputs <- Create_MainInputsServer("create_inputs", bds_metrics)
+ create_inputs <- Create_MainInputsServer(
+ "create_inputs",
+ topic_indicator_full
+ )
# Year range input ----------------------------------------------------------
year_input <- YearRangeServer(
diff --git a/tests/testthat/test-UI-app_inputs.R b/tests/testthat/test-UI-app_inputs.R
index fac3020..e3bddb9 100644
--- a/tests/testthat/test-UI-app_inputs.R
+++ b/tests/testthat/test-UI-app_inputs.R
@@ -39,7 +39,7 @@ minimal_server <- function(input, output, session) {
topic = NULL,
indicator = NULL
)
- appInputsServer("la_level", shared_values, bds_metrics, topic_indicator_full)
+ appInputsServer("la_level", shared_values, topic_indicator_full)
}
minimal_app <- shinyApp(minimal_ui, minimal_server)
diff --git a/tests/testthat/test-app_mod_la_lvl_table/app.R b/tests/testthat/test-app_mod_la_lvl_table/app.R
index b5050e1..bfecac2 100644
--- a/tests/testthat/test-app_mod_la_lvl_table/app.R
+++ b/tests/testthat/test-app_mod_la_lvl_table/app.R
@@ -25,7 +25,6 @@ minimal_server <- function(input, output, session) {
app_inputs <- appInputsServer(
"la_level",
shared_values,
- bds_metrics,
topic_indicator_full
)