API reference · assignments/hw3.py · 2574 lines
assignments.hw3
Assignment 3 autograder — evictions, code violations and the NDVI.
Graded against assignment_template/assignment-3.ipynb.
Why this grader looks nothing like the other two
Assignments 1 and 2 have no answer key: one class downloads its own Zillow extract, the other picks its own dataset. Assignment 3 hands out one zip file and everyone uses it, so for the first time the right answers are known. Philadelphia has 384 census tracts in PA-tracts.geojson; the fifteen listed violation types cover 34,108 rows of li_violations.csv; the median NDVI inside the city limits is 0.2025. Those are facts about the shipped data, so the checks below compare against them rather than inferring intent.
That also changes what confidence means here. In Assignment 1 several frames may plausibly be "the Philadelphia subset", and the ambiguity is the finding. Here a frame either carries 384 Philadelphia tracts or it does not; if one does, the step demonstrably happened, and it does not matter which variable holds it. So a matched expected value scores at confidence 1.0, and confidence drops only where the grader is choosing between readings of a partial answer.
Where the evidence comes from
Two places, and every item is graded on both.
The first is the notebook the student handed in. Re-running is the fragile part here — five data files read from whatever relative path each student used — and when the placement misses, the notebook dies at the first read_file and a correct submission grades as a column of zeros. Their own run did not have that problem, and the file carries what it printed: 384, 34108, Median NDVI, city: 0.2025, and :HoloMap [year] for the chart in 1.1.5. grader/answers.py harvests those, and the evaluators at the bottom of this module read them.
The second is the probe payload, extended for this assignment (see grader/probe_runtime.py): GeoDataFrames now carry their CRS, geometry types and total area; numpy arrays carry NaN-aware statistics, which is the only way to describe an NDVI array that is mostly NaN; shapely geometries and open rasterio datasets are described in place.
The plots are the interesting part. An hvplot call returns a holoviews object that knows what it is — a HoloMap carries the dimension its widget selects and one frame per value of it, a Layout carries its row/column shape — and those objects are read out of the kernel, Out included, because df.hvplot(...) on the last line of a cell is never bound to a name. So groupby= and dynamic=False are graded by their effect rather than by searching the source for the keywords.
The matplotlib figures (the hex bin map, the two tree plots) have no such structure, only a picture, so those reuse grader/charts.py exactly as Assignment 2 does — which already merges the executed and submitted notebooks — and the one item the assignment marks on styling comes back for a person to confirm.
The two readings are combined in grade() by taking the better of them: a step counts as done if either shows the right answer. Neither is reliably better, so neither is ranked above the other; what differs is the confidence attached, and whether a TA is told which one carried the item.
Summary
| Name | Kind | Description |
|---|---|---|
HW3Grader | class |
Constants
| Name | Value | Notes |
|---|---|---|
WIDGET_CONTAINERS | ('HoloMap', 'GridSpace', 'NdLayout') | holoviews container types that mean "one chart per value of a dimension". |
LAZY_CONTAINERS | ('DynamicMap',) | The same thing, but never evaluated: hvplot returns this when dynamic=False is left out, and it holds no frames at all. |
LAYOUT_CONTAINERS | ('Layout', 'NdLayout') |
Classes
No description.
Class attributes
| Name | Type | Default | Notes |
|---|---|---|---|
assignment_id | | 'hw3' | |
SUBMITTED_CONFIDENCE | | 0.9 | Confidence for a step seen only in the student's own saved output: above the review threshold, so it does not flag a correct submission on its own, but below the 1.0 of a result this grading run produced itself. A saved output is the student's claim about their run — it can be stale, and it can be edited by hand. |
NARROW_FRAME_COLUMNS | | 12 | An Eviction Lab frame carries 399 numeric columns, and across 3,217 tracts one of them will sum to almost any total by coincidence. A result of a group-by or a merge is narrow, so width is the guard: it keeps the raw file out of searches that look for a frame by what its numbers add up to. |
Methods
- answers(ctx: GradingContext) -> answerlib.NotebookAnswers #[source]
What the notebooks show, read once per submission and reused.
- grade(ctx: GradingContext) -> list[RubricItemResult] #[source]
Grade on the objects this run produced and on what the file shows.
The two readings are independent and neither is reliably better, so they are combined by taking the better of them rather than by ranking them: a step counts as done if the executed run shows the right answer or the submitted notebook does. That is what stops a data path the grader could not resolve from turning a correct submission into a column of zeros.
- charts(ctx: GradingContext) -> chartlib.ChartEvidence #[source]
matplotlib figures, read once per submission and reused.
Rubric checks 22
Each check_<rubric_id> method grades the rubric item with that id and returns one RubricItemResult.
- check_eviction_data_loading(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
3 ptsstructural1.1.1 Eviction data loaded with geopandasrubrics/hw3.yaml
PA-tracts.geojson is read with geopandas from a relative path.
Config keys:
absolute_path_penaltyexpected_rowsmissing_read_penaltynot_geodataframe_penaltyrequired_importsrow_tolerancewrong_size_penalty
- check_philadelphia_tracts(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.1.2 Data trimmed to Philadelphiarubrics/hw3.yaml
The Pennsylvania tracts are trimmed to the 384 tracts of Philadelphia County. The file has no city column — the county lives in
pl("Philadelphia County, Pennsylvania") and in the 42101 prefix of GEOID.Config keys:
expected_rowsgeoid_prefiximpure_credit_ratioincomplete_credit_ratiomin_purity
- check_tidy_evictions(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
6 ptsdeterministic1.1.3 Melted to tidy formatrubrics/hw3.yaml
pd.melt() over the fourteen eviction columns e-03 to e-16, keeping GEOID and geometry as id_vars — 384 tracts x 14 years = 5,376 rows.
Config keys:
expected_rowsexpected_value_sumexpected_year_labelsmissing_geometry_credit_ratiorequired_id_columnsvalue_column_hintsvalue_sum_rel_tolwrong_year_span_credit_ratio
- check_evictions_by_year_plot(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.1.4 Yearly eviction trend plotted with hvplotrubrics/hw3.yaml
A groupby over the year column, summed, plotted with hvplot — any chart type. Graded on the numbers actually plotted.
Config keys:
accepted_elementsexpected_maxexpected_pointsexpected_totaltotal_rel_tolwrong_library_credit_ratiowrong_values_credit_ratio
- check_evictions_choropleth(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.1.5 Year-by-year eviction choropleth with a widgetrubrics/hw3.yaml
An hvplot choropleth of the tidy frame with
groupby=on the year column anddynamic=False, producing a HoloMap with one frame per year.Config keys:
dynamic_map_credit_ratioexpected_framesexpected_keyskey_dimension_hintsno_widget_credit_ratiopolygon_elementswrong_dimension_credit_ratio
- check_violations_choropleth(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.2.7 Choropleth per violation type with a widgetrubrics/hw3.yaml
An hvplot choropleth with
groupby=on the violation type anddynamic=False— a HoloMap with one frame per type.Config keys:
dynamic_map_credit_ratioexpected_frameskey_dimension_hintsno_widget_credit_ratiopolygon_elementswrong_dimension_credit_ratio
- check_violations_loading(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic1.2.1 Violations loaded and converted to a GeoDataFramerubrics/hw3.yaml
li_violations.csv read with pandas and converted to a GeoDataFrame of 434,052 points built from the lng/lat columns, with a CRS set.
Config keys:
bounds_toleranceexpected_boundsexpected_crs_epsgexpected_geometryexpected_rowsmissing_crs_credit_rationot_geodataframe_credit_ratiorow_toleranceswapped_coordinates_credit_ratio
- check_violations_subset(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic1.2.2 Trimmed to the fifteen maintenance violation typesrubrics/hw3.yaml
The listed fifteen violation types, which cover 34,108 of the 434,052 rows.
Config keys:
expected_rowsexpected_type_countpartial_types_credit_ratioviolation_types
- check_hexbin_map(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic1.2.3 Hex bin map with census tracts overlaidrubrics/hw3.yaml
matplotlib's hexbin() over the violation points with the tract boundaries drawn on top, in a projected CRS rather than EPSG:4326.
Config keys:
hexbin_patternsmissing_overlay_credit_rationot_rendered_credit_ratiooverlay_patternsreprojection_patternsunprojected_credit_ratio
- check_spatial_join(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.2.4 Violations spatially joined to census tractsrubrics/hw3.yaml
geopandas.sjoin() attaches a GEOID to each of the 34,108 violations. Every one of them falls inside a Philadelphia tract, so a correct join loses no rows and leaves no GEOID missing.
Config keys:
expected_rowsgeoid_prefixlossy_join_credit_ratiorequires_columnsrow_toleranceunfiltered_expected_rowsunfiltered_join_credit_ratio
- check_violations_by_tract(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic1.2.5 Violations counted per type and tractrubrics/hw3.yaml
A groupby over violation type and GEOID giving a three-column frame (violationdescription, GEOID, N) whose counts sum to 34,108.
Config keys:
allowed_row_countscount_column_hintsexpected_count_sumexpected_type_countpartial_grouping_credit_ratiorequired_columns
- check_merge_geometries(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
3 ptsdeterministic1.2.6 Counts merged back onto tract geometriesrubrics/hw3.yaml
pandas.merge() with the tract geometries first, so the result is a GeoDataFrame and not a plain DataFrame.
Config keys:
allowed_row_countsnot_geodataframe_credit_ratiorequire_geodataframe
- check_side_by_side(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic1.3 Evictions and violations side by siderubrics/hw3.yaml
2016 evictions and a single violation type, drawn as two choropleths and composed into one row of two columns.
Config keys:
eviction_total_rel_tolexpected_columnsexpected_eviction_year_totalnot_composed_credit_ratiopolygon_elementsstill_grouped_credit_ratiowrong_orientation_credit_ratiowrong_subset_credit_ratio
- check_landsat_loading(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
3 ptsdeterministic2.1.1 Landsat scene opened with rasteriorubrics/hw3.yaml
The ten-band Landsat 8 scene is opened with rasterio.
Config keys:
expected_array_shapeexpected_band_countexpected_crs_epsgexpected_heightexpected_widthwrong_raster_credit_ratio
- check_city_suburb_polygons(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
5 ptsdeterministic2.1.2 City and suburb polygons builtrubrics/hw3.yaml
The city limits polygon, and a suburbs polygon made by subtracting it from its own envelope. Both must be in the raster's CRS for the mask to work.
Config keys:
area_rel_tolenvelope_not_differenced_credit_ratioexpected_city_areaexpected_suburb_areaexpected_suburb_geom_typesmissing_suburbs_credit_ratiounprojected_credit_ratio
- check_ndvi_masking(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
6 ptsdeterministic2.1.3 Raster masked and NDVI computed for each arearubrics/hw3.yaml
rasterio.mask.mask() applied with each polygon, and NDVI computed from the red and near-infrared bands of the masked array.
Config keys:
expected_city_medianexpected_city_pixelsexpected_suburb_medianexpected_suburb_pixelsmask_patternsmedian_abs_tolone_area_credit_ratiopixel_rel_tolvalid_rangewrong_bands_credit_ratio
- check_ndvi_medians(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic2.1.4 Median NDVI compared between city and suburbsrubrics/hw3.yaml
np.nanmedian() over each NDVI array, printed, with the suburbs coming out greener than the city.
Config keys:
expect_suburbs_greaterexpected_city_medianexpected_suburb_medianmedian_abs_tolnan_result_credit_ratioprint_patternsright_direction_credit_ratio
- check_tree_data_loading(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
3 ptsdeterministic2.2.1 Street tree data loadedrubrics/hw3.yaml
The 2,480 street tree points are loaded with geopandas.
Config keys:
expected_geometryexpected_rowsraster_crs_epsgrow_toleranceunprojected_credit_ratio
- check_tree_ndvi(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic2.2.2 NDVI sampled at the tree locationsrubrics/hw3.yaml
rasterstats used to read the NDVI at each of the 2,480 points, giving one value per tree.
Config keys:
alternative_patternscount_toleranceexpected_countexpected_meanexpected_medianmax_nan_fractionmedian_abs_tolrasterstats_patternsvalid_rangewrong_values_credit_ratio
- check_tree_plots(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
4 ptsdeterministic2.2.3 Histogram and mapped tree pointsrubrics/hw3.yaml
A matplotlib histogram of the NDVI values with a vertical line at zero, and a geopandas plot of the tree points coloured by NDVI with the city limits drawn on it.
Config keys:
boundary_patternsdetail_sharehistogram_patternshistogram_sharenot_rendered_credit_ratiopoint_plot_patternszero_line_patterns
- check_tree_plot_styling(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
3 ptsqualitative2.2.3 Figure styling (labels, legend, colour choices)rubrics/hw3.yaml
The one item on this assignment that a person has to look at.
The assignment asks for figures that are "clear and well-styled", which is a statement about a picture. The signals below say where to look; they cannot say whether the picture reads, so this always comes back as manual review with a provisional score rather than a silent mark.
Config keys:
provisional_ceiling_ratioweighted_signals
- check_common_violations_extra_credit(ctx: GradingContext, item: RubricItem) -> RubricItemResult #[source]
0 ptsdeterministicExtra credit: the twenty most common violation typesrubrics/hw3.yaml
The twenty most common violation types over 2012-2016 mapped as a second set of interactive choropleths, with three types named as having little spatial overlap with evictions. Worth no points in the rubric total; when it is found, the item is flagged so a TA can award the bonus by hand.
Config keys:
expected_count_sumexpected_framesmin_discussion_wordssuggested_points