fonte: https://www.vishalkatti.com/posts/2021-07-27-drilldown/
With some additional tweaks, we can make it look better.
reactable(
data = combined_df,
compact = TRUE, # for minimum row height
filterable = TRUE, # for individual column filters
striped = TRUE, # banded rows
resizable = TRUE, # for resizable column widths
columns = list( # define custom header name, width, alignment etc.
course_id = colDef(name = "CID", width = 50, align = "center"),
course_name = colDef(name = "Course Name", width = 140),
start_date = colDef(name = "Start Date", width = 120, align = "center"),
end_date = colDef(name = "End Date", width = 120, align = "center"),
s_id = colDef(name = "SID", width = 70, align = "center"),
s_name = colDef(name = "Student Name"),
gender = colDef(name = "Gender", width = 80, align = "center"),
age = colDef(name = "Age", width = 50)
)
)
However, the problem of repeating top-level fields still persists. Grouping and Aggregating
{reactable} has a groupBy argument which lets us combined rows with common data fields and the aggregate argument inside colDef lets us define what aggregation to be used for each column of the top-level data.
reactable(
data = combined_df,
compact = TRUE, # for minimum row height
filterable = TRUE, # for individual column filters
striped = TRUE, # banded rows
resizable = TRUE, # for resizable column widths
groupBy = "course_id",
columns = list(
# show count of students in each course
course_id = colDef(name = "CID", width = 100, align = "left", aggregate = "count"),
# show unique course name
course_name = colDef(name = "Course Name", width = 140, aggregate = "unique"),
# show unique start date
start_date = colDef(name = "Start Date", width = 120, align = "center", aggregate = "unique"),
# show unique end date
end_date = colDef(name = "End Date", width = 120, align = "center", aggregate = "unique"),
s_id = colDef(name = "SID", width = 70, align = "center"),
s_name = colDef(name = "Student Name"),
gender = colDef(name = "Gender", width = 80, align = "center"),
age = colDef(name = "Age", width = 50)
)
)
In this case, all the columns which are not aggregated remain hidden. Clicking the little triangle in the CID column displays the hidden rows. Looks better, but again, the issue of duplicated data remains.
You can aggregate the second-level columns too, but this distorts the table and frankly, looks ugly. Here I aggregate the SID column in addition to all the other top-level columns.
reactable(
data = combined_df,
compact = TRUE, # for minimum row height
filterable = TRUE, # for individual column filters
striped = TRUE, # banded rows
resizable = TRUE, # for resizable column widths
groupBy = "course_id",
columns = list(
course_id = colDef(name = "CID", width = 100, align = "left", aggregate = "count"),
course_name = colDef(name = "Course Name", width = 140, aggregate = "unique"),
start_date = colDef(name = "Start Date", width = 120, align = "center", aggregate = "unique"),
end_date = colDef(name = "End Date", width = 120, align = "center", aggregate = "unique"),
# YIKES!! Aggregating Student ID to show unique ids in each course.
s_id = colDef(name = "SID", width = 70, align = "center", aggregate = "unique"),
s_name = colDef(name = "Student Name"),
gender = colDef(name = "Gender", width = 80, align = "center"),
age = colDef(name = "Age", width = 50)
)
)
Wouldn’t it be nice if we could display only the top-level columns by default and on clicking the small triangle for a row, show all the second-level columns corresponding to that row only, like a drill-down table?
To do this we need 2 separate tables. Earlier in this post, I said it is advisable to split such denormalized data into normalized data i.e. create the original top-level and second level tables from the combined_df. Let’s recreate the 2 tables.
I want to demonstrate how we go from the combined data to the 2 tables. Hence I will not use the course and student tables created earlier.
Creating the top_level table using just the columns in course. Let’s also create a new column n_students depicting count of students in each course.
top_level <- combined_df %>%
# Only course info columns
count(course_id, course_name, start_date, end_date, name = "n_students")
paged_table(top_level)
second_level <- combined_df %>%
# Only Student info columns with unique identifier for Course
select(course_id, s_id, s_name, gender, age) %>%
arrange(s_id)
paged_table(second_level)
Now that we have the 2 tables ready, let us now create the final {reactable}. The trick here is to use the details argument to which we pass another {reactable} of just the rows with students data corresponding to given course.
reactable(
data = top_level,
compact = TRUE, # for minimum row height
filterable = TRUE, # for individual column filters
striped = TRUE, # banded rows
resizable = TRUE, # for resizable column widths
columns = list(
course_id = colDef(name = "CID", width = 50, align = "center"),
course_name = colDef(name = "Course Name"),
start_date = colDef(name = "Start Date", width = 120, align = "center"),
end_date = colDef(name = "End Date", width = 120, align = "center"),
n_students = colDef(name = "No. of Students", width = 130, align = "center")
),
details = function(index) { # index is the row number of current row.
# sub-table of only those students for current row.
sec_lvl = second_level[second_level$course_id == top_level$course_id[index], ]
reactable(data = sec_lvl,
compact = TRUE,
filterable = TRUE,
bordered = TRUE,
resizable = TRUE,
columns = list(
course_id = colDef(show = FALSE), # hide the course id column
s_id = colDef(name = "SID", width = 70, align = "center"),
s_name = colDef(name = "Student Name"),
gender = colDef(name = "Gender", width = 90, align = "center"),
age = colDef(name = "Age", width = 50, align = "center")
)
)
}
)
Since the sub-table is also a {reactable}, you can go another level down… and another, but please do consider the usability aspect of this feature before taking that decision. I haven’t tried going beyond 2 levels of data myself. Maybe a part 2 to this post?? Conclusion
Drill-down tables let you pack a lot of data in a compact manner and allow use by multiple audiences interested in varying degrees/levels of information. {reactable} can help create an interactive data table from tabular data with sorting and pagination by default. The data table is an HTML widget that can be used in R Markdown documents and Shiny applications, or viewed from an R console. A lot of features can be enabled/disabled using the basic arguments of the reactable() function and much more using custom JavaScript.