back to Claude Sonnet 3.5 - Fill-in summary
Claude Sonnet 3.5 - Fill-in: seaborn
Pytest Summary for test tests
status | count |
---|---|
failed | 214 |
passed | 29 |
total | 243 |
collected | 243 |
Failed pytests:
test_properties.py::TestColor::test_nominal_default_palette_large
test_properties.py::TestColor::test_nominal_default_palette_large
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_faceted_fill
test_moves.py::TestDodge::test_faceted_fill
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_default_no_groups[x]
test_moves.py::TestNorm::test_default_no_groups[x]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_default
test_moves.py::TestDodge::test_default
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_at_least_one_grouping_variable_required
test_groupby.py::test_at_least_one_grouping_variable_required
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_default_no_groups[y]
test_moves.py::TestNorm::test_default_no_groups[y]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_named_palette
test_properties.py::TestColor::test_nominal_named_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_default_groups[x]
test_moves.py::TestNorm::test_default_groups[x]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_agg_one_grouper
test_groupby.py::test_agg_one_grouper
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_agg_two_groupers
test_groupby.py::test_agg_two_groupers
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_default_groups[y]
test_moves.py::TestNorm::test_default_groups[y]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_list_palette
test_properties.py::TestColor::test_nominal_list_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_agg_two_groupers_ordered
test_groupby.py::test_agg_two_groupers_ordered
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_apply_no_grouper
test_groupby.py::test_apply_no_grouper
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_sum
test_moves.py::TestNorm::test_sum
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_dict_palette
test_properties.py::TestColor::test_nominal_dict_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_where
test_moves.py::TestNorm::test_where
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_dict_with_missing_keys
test_properties.py::TestColor::test_nominal_dict_with_missing_keys
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_list_too_short
test_properties.py::TestColor::test_nominal_list_too_short
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestNorm::test_percent
test_moves.py::TestNorm::test_percent
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_apply_one_grouper
test_groupby.py::test_apply_one_grouper
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_apply_mutate_columns
test_groupby.py::test_apply_mutate_columns
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestCoordinate::test_bad_scale_arg_str
test_properties.py::TestCoordinate::test_bad_scale_arg_str
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_groupby.py::test_apply_replace_columns
test_groupby.py::test_apply_replace_columns
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_list_too_long
test_properties.py::TestColor::test_nominal_list_too_long
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestCoordinate::test_bad_scale_arg_type
test_properties.py::TestCoordinate::test_bad_scale_arg_type
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[viridis-bool-Boolean]
test_properties.py::TestColor::test_inference[viridis-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_nominal_default_palette
test_properties.py::TestColor::test_nominal_default_palette
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[muted-num-Nominal]
test_properties.py::TestColor::test_inference[muted-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_continuous_default_palette
test_properties.py::TestColor::test_continuous_default_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values4-num-Nominal]
test_properties.py::TestColor::test_inference[values4-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_fill
test_moves.py::TestDodge::test_fill
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_faceted_drop
test_moves.py::TestDodge::test_faceted_drop
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_default[num]
test_properties.py::TestMarker::test_default[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_continuous_named_palette
test_properties.py::TestColor::test_continuous_named_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values5-num-Nominal]
test_properties.py::TestColor::test_inference[values5-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_default[bool]
test_properties.py::TestMarker::test_default[bool]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_continuous_tuple_palette
test_properties.py::TestColor::test_continuous_tuple_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values6-num-Continuous]
test_properties.py::TestColor::test_inference[values6-num-Continuous]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_list[cat]
test_properties.py::TestMarker::test_inference_list[cat]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_list[num]
test_properties.py::TestMarker::test_inference_list[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values7-cat-Nominal]
test_properties.py::TestColor::test_inference[values7-cat-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_list[bool]
test_properties.py::TestMarker::test_inference_list[bool]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values8-bool-Boolean]
test_properties.py::TestColor::test_inference[values8-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_continuous_callable_palette
test_properties.py::TestColor::test_continuous_callable_palette
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_dict[cat]
test_properties.py::TestMarker::test_inference_dict[cat]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[values9-num-Continuous]
test_properties.py::TestColor::test_inference[values9-num-Continuous]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_continuous_missing
test_properties.py::TestColor::test_continuous_missing
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_standardization
test_properties.py::TestColor::test_standardization
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_dict[num]
test_properties.py::TestMarker::test_inference_dict[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_default[cat]
test_properties.py::TestMarker::test_mapping_default[cat]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_default[num]
test_properties.py::TestLineStyle::test_default[num]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_inference_dict[bool]
test_properties.py::TestMarker::test_inference_dict[bool]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_default[num]
test_properties.py::TestMarker::test_mapping_default[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_default[bool]
test_properties.py::TestLineStyle::test_default[bool]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_dict_missing
test_properties.py::TestMarker::test_dict_missing
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_list[cat]
test_properties.py::TestLineStyle::test_inference_list[cat]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_from_list[cat]
test_properties.py::TestMarker::test_mapping_from_list[cat]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_dict[bool]
test_properties.py::TestLineStyle::test_inference_dict[bool]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_from_list[num]
test_properties.py::TestMarker::test_mapping_from_list[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_dict_missing
test_properties.py::TestLineStyle::test_dict_missing
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_from_dict[cat]
test_properties.py::TestMarker::test_mapping_from_dict[cat]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_from_dict[num]
test_properties.py::TestMarker::test_mapping_from_dict[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_default[cat]
test_properties.py::TestLineStyle::test_mapping_default[cat]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_list[num]
test_properties.py::TestLineStyle::test_inference_list[num]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_mapping_with_null_value
test_properties.py::TestMarker::test_mapping_with_null_value
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_unique_default_large_n
test_properties.py::TestMarker::test_unique_default_large_n
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_list[bool]
test_properties.py::TestLineStyle::test_inference_list[bool]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_default[num]
test_properties.py::TestLineStyle::test_mapping_default[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_drop
test_moves.py::TestDodge::test_drop
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_dict[cat]
test_properties.py::TestLineStyle::test_inference_dict[cat]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_orient
test_moves.py::TestDodge::test_orient
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_inference_dict[num]
test_properties.py::TestLineStyle::test_inference_dict[num]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_from_list[cat]
test_properties.py::TestLineStyle::test_mapping_from_list[cat]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_with_null_value
test_properties.py::TestLineStyle::test_mapping_with_null_value
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_from_list[num]
test_properties.py::TestLineStyle::test_mapping_from_list[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_unique_default_large_n
test_properties.py::TestLineStyle::test_unique_default_large_n
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_from_dict[cat]
test_properties.py::TestLineStyle::test_mapping_from_dict[cat]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_bad_scale_values
test_properties.py::TestLineStyle::test_bad_scale_values
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_bad_type
test_properties.py::TestLineStyle::test_bad_type
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_bad_style
test_properties.py::TestLineStyle::test_bad_style
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_mapping_from_dict[num]
test_properties.py::TestLineStyle::test_mapping_from_dict[num]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineStyle::test_bad_dashes
test_properties.py::TestLineStyle::test_bad_dashes
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_dict[bool]
test_properties.py::TestFill::test_inference_dict[bool]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestMarker::test_bad_scale_values
test_properties.py::TestMarker::test_bad_scale_values
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_values_error
test_properties.py::TestFill::test_values_error
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_mapping_categorical_data
test_properties.py::TestFill::test_mapping_categorical_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_default[num]
test_properties.py::TestFill::test_default[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_default[bool-Boolean]
test_properties.py::TestAlpha::test_default[bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_mapping_numeric_data
test_properties.py::TestFill::test_mapping_numeric_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_default[bool]
test_properties.py::TestFill::test_default[bool]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg0-cat-Nominal]
test_properties.py::TestAlpha::test_inference[arg0-cat-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_list[cat]
test_properties.py::TestFill::test_inference_list[cat]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_mapping_list
test_properties.py::TestFill::test_mapping_list
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_list[num]
test_properties.py::TestFill::test_inference_list[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg1-num-Continuous]
test_properties.py::TestAlpha::test_inference[arg1-num-Continuous]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_mapping_truthy_list
test_properties.py::TestFill::test_mapping_truthy_list
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_list[bool]
test_properties.py::TestFill::test_inference_list[bool]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_mapping_dict
test_properties.py::TestFill::test_mapping_dict
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg2-bool-Boolean]
test_properties.py::TestAlpha::test_inference[arg2-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_cycle_warning
test_properties.py::TestFill::test_cycle_warning
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_dict[cat]
test_properties.py::TestFill::test_inference_dict[cat]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_gap
test_moves.py::TestDodge::test_gap
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg8-bool-Boolean]
test_properties.py::TestAlpha::test_inference[arg8-bool-Boolean]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg3-cat-Nominal]
test_properties.py::TestAlpha::test_inference[arg3-cat-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestFill::test_inference_dict[num]
test_properties.py::TestFill::test_inference_dict[num]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_single_semantic[grp2]
test_moves.py::TestDodge::test_single_semantic[grp2]
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_mapped_interval_numeric
test_properties.py::TestAlpha::test_mapped_interval_numeric
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_default[bool-Boolean]
test_properties.py::TestLineWidth::test_default[bool-Boolean]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg4-num-Nominal]
test_properties.py::TestAlpha::test_inference[arg4-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg0-cat-Nominal]
test_properties.py::TestLineWidth::test_inference[arg0-cat-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_mapped_interval_categorical
test_properties.py::TestAlpha::test_mapped_interval_categorical
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg5-bool-Boolean]
test_properties.py::TestAlpha::test_inference[arg5-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_bad_scale_values_numeric_data
test_properties.py::TestAlpha::test_bad_scale_values_numeric_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg1-num-Continuous]
test_properties.py::TestLineWidth::test_inference[arg1-num-Continuous]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg6-cat-Nominal]
test_properties.py::TestAlpha::test_inference[arg6-cat-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_bad_scale_values_categorical_data
test_properties.py::TestAlpha::test_bad_scale_values_categorical_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg2-bool-Boolean]
test_properties.py::TestLineWidth::test_inference[arg2-bool-Boolean]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_mapped_interval_numeric
test_properties.py::TestLineWidth::test_mapped_interval_numeric
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestAlpha::test_inference[arg7-num-Nominal]
test_properties.py::TestAlpha::test_inference[arg7-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_mapped_interval_categorical
test_properties.py::TestLineWidth::test_mapped_interval_categorical
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg3-cat-Nominal]
test_properties.py::TestLineWidth::test_inference[arg3-cat-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg4-num-Nominal]
test_properties.py::TestLineWidth::test_inference[arg4-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_bad_scale_values_numeric_data
test_properties.py::TestLineWidth::test_bad_scale_values_numeric_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg5-bool-Boolean]
test_properties.py::TestLineWidth::test_inference[arg5-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_bad_scale_values_categorical_data
test_properties.py::TestLineWidth::test_bad_scale_values_categorical_data
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_default[bool-Boolean]
test_properties.py::TestEdgeWidth::test_default[bool-Boolean]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_rcparam_default
test_properties.py::TestLineWidth::test_rcparam_default
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg0-cat-Nominal]
test_properties.py::TestEdgeWidth::test_inference[arg0-cat-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg6-cat-Nominal]
test_properties.py::TestLineWidth::test_inference[arg6-cat-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg2-bool-Boolean]
test_properties.py::TestEdgeWidth::test_inference[arg2-bool-Boolean]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg1-num-Continuous]
test_properties.py::TestEdgeWidth::test_inference[arg1-num-Continuous]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg3-cat-Nominal]
test_properties.py::TestEdgeWidth::test_inference[arg3-cat-Nominal]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg7-num-Nominal]
test_properties.py::TestLineWidth::test_inference[arg7-num-Nominal]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg6-cat-Nominal]
test_properties.py::TestEdgeWidth::test_inference[arg6-cat-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg4-num-Nominal]
test_properties.py::TestEdgeWidth::test_inference[arg4-num-Nominal]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestLineWidth::test_inference[arg8-bool-Boolean]
test_properties.py::TestLineWidth::test_inference[arg8-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg7-num-Nominal]
test_properties.py::TestEdgeWidth::test_inference[arg7-num-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg5-bool-Boolean]
test_properties.py::TestEdgeWidth::test_inference[arg5-bool-Boolean]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_mapped_interval_categorical
test_properties.py::TestEdgeWidth::test_mapped_interval_categorical
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_inference[arg8-bool-Boolean]
test_properties.py::TestEdgeWidth::test_inference[arg8-bool-Boolean]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_bad_scale_values_numeric_data
test_properties.py::TestEdgeWidth::test_bad_scale_values_numeric_data
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_default[bool-Boolean]
test_properties.py::TestPointSize::test_default[bool-Boolean]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_bad_scale_values_categorical_data
test_properties.py::TestEdgeWidth::test_bad_scale_values_categorical_data
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_widths_default
test_moves.py::TestDodge::test_widths_default
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_mapped_interval_numeric
test_properties.py::TestEdgeWidth::test_mapped_interval_numeric
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg0-cat-Nominal]
test_properties.py::TestPointSize::test_inference[arg0-cat-Nominal]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestEdgeWidth::test_rcparam_default
test_properties.py::TestEdgeWidth::test_rcparam_default
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg8-bool-Boolean]
test_properties.py::TestPointSize::test_inference[arg8-bool-Boolean]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg1-num-Continuous]
test_properties.py::TestPointSize::test_inference[arg1-num-Continuous]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg5-bool-Boolean]
test_properties.py::TestPointSize::test_inference[arg5-bool-Boolean]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_mapped_interval_numeric
test_properties.py::TestPointSize::test_mapped_interval_numeric
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_single_semantic[grp3]
test_moves.py::TestDodge::test_single_semantic[grp3]
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg6-cat-Nominal]
test_properties.py::TestPointSize::test_inference[arg6-cat-Nominal]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg2-bool-Boolean]
test_properties.py::TestPointSize::test_inference[arg2-bool-Boolean]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_mapped_interval_categorical
test_properties.py::TestPointSize::test_mapped_interval_categorical
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg7-num-Nominal]
test_properties.py::TestPointSize::test_inference[arg7-num-Nominal]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg3-cat-Nominal]
test_properties.py::TestPointSize::test_inference[arg3-cat-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_bad_scale_values_numeric_data
test_properties.py::TestPointSize::test_bad_scale_values_numeric_data
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_bad_scale_values_categorical_data
test_properties.py::TestPointSize::test_bad_scale_values_categorical_data
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_areal_scaling_categorical
test_properties.py::TestPointSize::test_areal_scaling_categorical
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_inference[arg4-num-Nominal]
test_properties.py::TestPointSize::test_inference[arg4-num-Nominal]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_rules.py::test_categorical_order
test_rules.py::test_categorical_order
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_rules.py::test_variable_type
test_rules.py::test_variable_type
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestPointSize::test_areal_scaling_numeric
test_properties.py::TestPointSize::test_areal_scaling_numeric
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSpecificationChecks::test_both_facets_and_wrap
test_subplots.py::TestSpecificationChecks::test_both_facets_and_wrap
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSpecificationChecks::test_wrapped_columns_and_y_pairing
test_subplots.py::TestSpecificationChecks::test_wrapped_columns_and_y_pairing
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_single_subplot
test_subplots.py::TestSubplotSpec::test_single_subplot
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSpecificationChecks::test_wrapped_x_pairing_and_facetd_rows
test_subplots.py::TestSpecificationChecks::test_wrapped_x_pairing_and_facetd_rows
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSpecificationChecks::test_cross_xy_pairing_and_wrap
test_subplots.py::TestSpecificationChecks::test_cross_xy_pairing_and_wrap
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_single_facet
test_subplots.py::TestSubplotSpec::test_single_facet
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_two_facets
test_subplots.py::TestSubplotSpec::test_two_facets
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSpecificationChecks::test_col_facets_and_x_pairing
test_subplots.py::TestSpecificationChecks::test_col_facets_and_x_pairing
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_col_facet_wrapped
test_subplots.py::TestSubplotSpec::test_col_facet_wrapped
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_row_facet_wrapped
test_subplots.py::TestSubplotSpec::test_row_facet_wrapped
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_x_paired
test_subplots.py::TestSubplotSpec::test_x_paired
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_col_facet_wrapped_single_row
test_subplots.py::TestSubplotSpec::test_col_facet_wrapped_single_row
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_x_and_y_paired
test_subplots.py::TestSubplotSpec::test_x_and_y_paired
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_y_paired
test_subplots.py::TestSubplotSpec::test_y_paired
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_y_paired_and_wrapped
test_subplots.py::TestSubplotSpec::test_y_paired_and_wrapped
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_x_paired_and_wrapped
test_subplots.py::TestSubplotSpec::test_x_paired_and_wrapped
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_y_paired_and_wrapped_single_row
test_subplots.py::TestSubplotSpec::test_y_paired_and_wrapped_single_row
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_col_faceted_y_paired
test_subplots.py::TestSubplotSpec::test_col_faceted_y_paired
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_row_faceted_x_paired
test_subplots.py::TestSubplotSpec::test_row_faceted_x_paired
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_x_any_y_paired_non_cross
test_subplots.py::TestSubplotSpec::test_x_any_y_paired_non_cross
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotSpec::test_x_any_y_paired_non_cross_wrapped
test_subplots.py::TestSubplotSpec::test_x_any_y_paired_non_cross_wrapped
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_facet_dim[row]
test_subplots.py::TestSubplotElements::test_single_facet_dim[row]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_subplot
test_subplots.py::TestSubplotElements::test_single_subplot
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_facet_dim[col]
test_subplots.py::TestSubplotElements::test_single_facet_dim[col]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_facet_dim_wrapped[col]
test_subplots.py::TestSubplotElements::test_single_facet_dim_wrapped[col]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_both_facet_dims
test_subplots.py::TestSubplotElements::test_both_facet_dims
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_facet_dim_wrapped[row]
test_subplots.py::TestSubplotElements::test_single_facet_dim_wrapped[row]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_paired_var[x]
test_subplots.py::TestSubplotElements::test_single_paired_var[x]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_paired_var[y]
test_subplots.py::TestSubplotElements::test_single_paired_var[y]
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_paired_var_wrapped[x]
test_subplots.py::TestSubplotElements::test_single_paired_var_wrapped[x]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_single_paired_var_wrapped[y]
test_subplots.py::TestSubplotElements::test_single_paired_var_wrapped[y]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_both_paired_non_cross
test_subplots.py::TestSubplotElements::test_both_paired_non_cross
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_both_paired_variables
test_subplots.py::TestSubplotElements::test_both_paired_variables
[gw3] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_one_facet_one_paired[col-y]
test_subplots.py::TestSubplotElements::test_one_facet_one_paired[col-y]
[gw4] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_subplots.py::TestSubplotElements::test_one_facet_one_paired[row-x]
test_subplots.py::TestSubplotElements::test_one_facet_one_paired[row-x]
[gw1] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_widths_fill
test_moves.py::TestDodge::test_widths_fill
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_two_semantics
test_moves.py::TestDodge::test_two_semantics
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_widths_drop
test_moves.py::TestDodge::test_widths_drop
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestStack::test_basic
test_moves.py::TestStack::test_basic
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestStack::test_faceted
test_moves.py::TestStack::test_faceted
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestStack::test_misssing_data
test_moves.py::TestStack::test_misssing_data
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestStack::test_baseline_homogeneity_check
test_moves.py::TestStack::test_baseline_homogeneity_check
[gw2] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_moves.py::TestDodge::test_faceted_default
test_moves.py::TestDodge::test_faceted_default
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_bad_scale_values_continuous
test_properties.py::TestColor::test_bad_scale_values_continuous
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_bad_scale_values_nominal
test_properties.py::TestColor::test_bad_scale_values_nominal
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_bad_inference_arg
test_properties.py::TestColor::test_bad_inference_arg
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_default[bool-Boolean]
test_properties.py::TestColor::test_default[bool-Boolean]
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[viridis-cat-Nominal]
test_properties.py::TestColor::test_inference[viridis-cat-Nominal]
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
test_properties.py::TestColor::test_inference[viridis-num-Continuous]
test_properties.py::TestColor::test_inference[viridis-num-Continuous]
[gw0] linux -- Python 3.12.6 /testbed/.venv/bin/python3
Patch diff
diff --git a/seaborn/_base.py b/seaborn/_base.py
index d7a46b61..98b73a20 100644
--- a/seaborn/_base.py
+++ b/seaborn/_base.py
@@ -25,11 +25,19 @@ class SemanticMapping:
def _check_list_length(self, levels, values, variable):
"""Input check when values are provided as a list."""
- pass
+ if len(values) != len(levels):
+ raise ValueError(f"The {variable} list has {len(values)} values,"
+ f" but there are {len(levels)} {variable} levels")
def _lookup_single(self, key):
- """Apply the mapping to a single data value."""
- pass
+ """Get the size for a single value, using norm to interpolate."""
+ if self.map_type == "numeric":
+ if self.norm is not None:
+ return self.size_range[0] + self.norm(key) * (self.size_range[1] - self.size_range[0])
+ else:
+ return self.lookup_table[key]
+ else:
+ return self.lookup_table[key]
def __call__(self, key, *args, **kwargs):
"""Get the attribute(s) values for the data key."""
@@ -93,19 +101,55 @@ class HueMapping(SemanticMapping):
def _lookup_single(self, key):
"""Get the color for a single value, using colormap to interpolate."""
- pass
+ if self.map_type == "numeric":
+ if self.norm is not None:
+ normed = self.norm(key)
+ return self.cmap(normed)
+ else:
+ return self.lookup_table[key]
+ else:
+ return self.lookup_table[key]
def infer_map_type(self, palette, norm, input_format, var_type):
"""Determine how to implement the mapping."""
- pass
+ if isinstance(palette, dict):
+ return "categorical"
+ elif norm is not None:
+ return "numeric"
+ elif var_type == "numeric":
+ return "numeric"
+ elif isinstance(palette, str) and palette in QUAL_PALETTES:
+ return "categorical"
+ else:
+ return "categorical"
def categorical_mapping(self, data, palette, order):
"""Determine colors when the hue mapping is categorical."""
- pass
+ levels = categorical_order(data, order)
+ n_colors = len(levels)
+
+ if isinstance(palette, dict):
+ palette = [palette[k] for k in levels]
+ else:
+ palette = color_palette(palette, n_colors)
+
+ palette = [desaturate(c, self.saturation) for c in palette]
+ lookup_table = dict(zip(levels, palette))
+
+ return levels, lookup_table
def numeric_mapping(self, data, palette, norm):
"""Determine colors when the hue variable is quantitative."""
- pass
+ levels = remove_na(data).unique()
+ if norm is None:
+ norm = mpl.colors.Normalize()
+ if isinstance(palette, str):
+ cmap = mpl.cm.get_cmap(palette)
+ else:
+ cmap = mpl.colors.ListedColormap(palette)
+
+ lookup_table = {l: cmap(norm(l)) for l in levels}
+ return levels, lookup_table, norm, cmap
class SizeMapping(SemanticMapping):
@@ -189,11 +233,32 @@ class StyleMapping(SemanticMapping):
def _lookup_single(self, key, attr=None):
"""Get attribute(s) for a given data point."""
- pass
+ if key not in self.lookup_table:
+ return {}
+ if attr is None:
+ return self.lookup_table[key]
+ elif attr in self.lookup_table[key]:
+ return self.lookup_table[key][attr]
+ else:
+ return None
def _map_attributes(self, arg, levels, defaults, attr):
"""Handle the specification for a given style attribute."""
- pass
+ if arg is True:
+ return dict(zip(levels, defaults))
+ elif isinstance(arg, dict):
+ missing = set(levels) - set(arg)
+ if missing:
+ msg = f"These {attr} levels are missing: {missing}"
+ raise ValueError(msg)
+ return arg
+ elif isinstance(arg, Sequence):
+ if len(arg) != len(levels):
+ msg = f"The {attr} list has {len(arg)} values but there are {len(levels)} {attr} levels"
+ raise ValueError(msg)
+ return dict(zip(levels, arg))
+ else:
+ return {}
class VectorPlotter:
@@ -214,7 +279,7 @@ class VectorPlotter:
@property
def has_xy_data(self):
"""Return True at least one of x or y is defined."""
- pass
+ return any(var in self.plot_data for var in ['x', 'y'])
@property
def var_levels(self):
@@ -229,7 +294,12 @@ class VectorPlotter:
tracking plot variables.
"""
- pass
+ for var in ["hue", "size", "style"]:
+ if hasattr(self, f"{var}_map"):
+ mapper = getattr(self, f"{var}_map")
+ if mapper.levels is not None:
+ self._var_levels[var] = list(mapper.levels)
+ return self._var_levels
def assign_variables(self, data=None, variables={}):
"""Define plot variables, optionally using lookup from `data`."""
diff --git a/seaborn/_compat.py b/seaborn/_compat.py
index 76dc5054..b41f939a 100644
--- a/seaborn/_compat.py
+++ b/seaborn/_compat.py
@@ -9,35 +9,72 @@ from seaborn.utils import _version_predates
def norm_from_scale(scale, norm):
"""Produce a Normalize object given a Scale and min/max domain limits."""
- pass
+ if isinstance(scale, mpl.scale.LogScale):
+ return mpl.colors.LogNorm(vmin=norm.vmin, vmax=norm.vmax)
+ elif isinstance(scale, mpl.scale.SymmetricalLogScale):
+ return mpl.colors.SymLogNorm(linthresh=scale.linthresh, linscale=scale.linscale,
+ vmin=norm.vmin, vmax=norm.vmax)
+ else:
+ return mpl.colors.Normalize(vmin=norm.vmin, vmax=norm.vmax)
def get_colormap(name):
"""Handle changes to matplotlib colormap interface in 3.6."""
- pass
+ if _version_predates(mpl, "3.6"):
+ return mpl.cm.get_cmap(name)
+ else:
+ return mpl.colormaps[name]
def register_colormap(name, cmap):
"""Handle changes to matplotlib colormap interface in 3.6."""
- pass
+ if _version_predates(mpl, "3.6"):
+ mpl.cm.register_cmap(name, cmap)
+ else:
+ mpl.colormaps.register(cmap, name=name)
def set_layout_engine(fig: Figure, engine: Literal['constrained',
'compressed', 'tight', 'none']) ->None:
"""Handle changes to auto layout engine interface in 3.6"""
- pass
+ if _version_predates(mpl, "3.6"):
+ if engine == 'none':
+ fig.set_tight_layout(False)
+ elif engine == 'tight':
+ fig.set_tight_layout(True)
+ else:
+ fig.set_constrained_layout(True)
+ else:
+ fig.set_layout_engine(engine)
def get_layout_engine(fig: Figure) ->(mpl.layout_engine.LayoutEngine | None):
"""Handle changes to auto layout engine interface in 3.6"""
- pass
+ if _version_predates(mpl, "3.6"):
+ if fig.get_tight_layout():
+ return 'tight'
+ elif fig.get_constrained_layout():
+ return 'constrained'
+ else:
+ return None
+ else:
+ return fig.get_layout_engine()
def share_axis(ax0, ax1, which):
"""Handle changes to post-hoc axis sharing."""
- pass
+ if _version_predates(mpl, "3.5"):
+ if which == 'x':
+ ax0.get_shared_x_axes().join(ax0, ax1)
+ elif which == 'y':
+ ax0.get_shared_y_axes().join(ax0, ax1)
+ else:
+ ax0.sharex(ax1) if which == 'x' else ax0.sharey(ax1)
def get_legend_handles(legend):
"""Handle legendHandles attribute rename."""
- pass
+ if _version_predates(mpl, "3.7"):
+ return legend.legendHandles
+ else:
+ return legend.legend_handles
diff --git a/seaborn/_core/data.py b/seaborn/_core/data.py
index e6ece7c9..f25682cd 100644
--- a/seaborn/_core/data.py
+++ b/seaborn/_core/data.py
@@ -64,7 +64,28 @@ class PlotData:
def join(self, data: DataSource, variables: (dict[str, VariableSpec] |
None)) ->PlotData:
"""Add, replace, or drop variables and return as a new dataset."""
- pass
+ new_data = handle_data_source(data)
+ if variables is None:
+ variables = {}
+
+ new_frame, new_names, new_ids = self._assign_variables(new_data, variables)
+
+ # Merge the new frame with the existing one
+ merged_frame = self.frame.join(new_frame, how='outer')
+
+ # Update names and ids
+ merged_names = {**self.names, **new_names}
+ merged_ids = {**self.ids, **new_ids}
+
+ # Create a new PlotData instance
+ new_plot_data = PlotData(merged_frame, {})
+ new_plot_data.frame = merged_frame
+ new_plot_data.names = merged_names
+ new_plot_data.ids = merged_ids
+ new_plot_data.source_data = {**self.source_data, **new_data} if isinstance(self.source_data, dict) else new_data
+ new_plot_data.source_vars = {**self.source_vars, **variables}
+
+ return new_plot_data
def _assign_variables(self, data: (DataFrame | Mapping | None),
variables: dict[str, VariableSpec]) ->tuple[DataFrame, dict[str,
@@ -102,14 +123,58 @@ class PlotData:
non-indexed vector datatypes that have a different length from `data`.
"""
- pass
+ if not isinstance(data, (pd.DataFrame, Mapping)) and data is not None:
+ raise TypeError("Data source must be a DataFrame, Mapping, or None")
+
+ frame = pd.DataFrame()
+ names = {}
+ ids = {}
+
+ for var_name, var_spec in variables.items():
+ if isinstance(var_spec, str):
+ if data is None or var_spec not in data:
+ raise ValueError(f"Variable '{var_spec}' not found in data")
+ frame[var_name] = data[var_spec]
+ names[var_name] = var_spec
+ ids[var_name] = id(data[var_spec])
+ else:
+ try:
+ series = pd.Series(var_spec, name=var_name)
+ if data is not None and len(series) != len(data):
+ raise ValueError(f"Length of variable '{var_name}' does not match data length")
+ frame[var_name] = series
+ names[var_name] = getattr(var_spec, 'name', None)
+ ids[var_name] = id(var_spec)
+ except Exception as e:
+ raise ValueError(f"Could not convert variable '{var_name}' to Series: {str(e)}")
+
+ return frame, names, ids
def handle_data_source(data: object) ->(pd.DataFrame | Mapping | None):
"""Convert the data source object to a common union representation."""
- pass
+ if data is None:
+ return None
+ elif isinstance(data, pd.DataFrame):
+ return data
+ elif isinstance(data, Mapping):
+ return data
+ elif hasattr(data, '__dataframe__'): # Check for DataFrame exchange protocol
+ return convert_dataframe_to_pandas(data)
+ else:
+ try:
+ return pd.DataFrame(data)
+ except Exception:
+ raise TypeError("Unable to convert data source to DataFrame or Mapping")
def convert_dataframe_to_pandas(data: object) ->pd.DataFrame:
"""Use the DataFrame exchange protocol, or fail gracefully."""
- pass
+ try:
+ df = data.__dataframe__()
+ if isinstance(df, pd.DataFrame):
+ return df
+ else:
+ return pd.DataFrame(df.to_pandas())
+ except Exception as e:
+ raise ValueError(f"Failed to convert data using DataFrame exchange protocol: {str(e)}")
diff --git a/seaborn/_core/exceptions.py b/seaborn/_core/exceptions.py
index b90716ec..c6db38aa 100644
--- a/seaborn/_core/exceptions.py
+++ b/seaborn/_core/exceptions.py
@@ -24,4 +24,7 @@ class PlotSpecError(RuntimeError):
"""
Initialize the class to report the failure of a specific operation.
"""
- pass
+ message = f"Error occurred during {step}"
+ if var:
+ message += f" for variable '{var}'"
+ return cls(message)
diff --git a/seaborn/_core/groupby.py b/seaborn/_core/groupby.py
index 89566c5e..451b35db 100644
--- a/seaborn/_core/groupby.py
+++ b/seaborn/_core/groupby.py
@@ -42,16 +42,33 @@ class GroupBy:
order = {k: None for k in order}
self.order = order
- def _get_groups(self, data: DataFrame) ->tuple[str | list[str], Index |
- MultiIndex]:
+ def _get_groups(self, data: DataFrame) -> tuple[str | list[str], Index | MultiIndex]:
"""Return index with Cartesian product of ordered grouping variable levels."""
- pass
+ groupers = []
+ levels = []
+ names = []
+ for var, order in self.order.items():
+ if var in data.columns:
+ groupers.append(data[var])
+ level = categorical_order(data[var], order)
+ levels.append(level)
+ names.append(var)
+
+ if len(names) == 1:
+ index = pd.Index(levels[0], name=names[0])
+ return names[0], index
+ else:
+ index = pd.MultiIndex.from_product(levels, names=names)
+ return names, index
def _reorder_columns(self, res, data):
"""Reorder result columns to match original order with new columns appended."""
- pass
+ original_cols = data.columns
+ new_cols = [col for col in res.columns if col not in original_cols]
+ reordered_cols = list(original_cols) + new_cols
+ return res.reindex(columns=reordered_cols)
- def agg(self, data: DataFrame, *args, **kwargs) ->DataFrame:
+ def agg(self, data: DataFrame, *args, **kwargs) -> DataFrame:
"""
Reduce each group to a single row in the output.
@@ -60,9 +77,27 @@ class GroupBy:
those combinations do not appear in the dataset.
"""
- pass
+ groupers, index = self._get_groups(data)
+ grouped = data.groupby(groupers, observed=False)
+ res = grouped.agg(*args, **kwargs)
+
+ if isinstance(res.index, pd.MultiIndex):
+ res = res.reindex(index)
+ else:
+ res = res.reindex(index.get_level_values(0))
+
+ return self._reorder_columns(res, data)
def apply(self, data: DataFrame, func: Callable[..., DataFrame], *args,
- **kwargs) ->DataFrame:
+ **kwargs) -> DataFrame:
"""Apply a DataFrame -> DataFrame mapping to each group."""
- pass
+ groupers, index = self._get_groups(data)
+ grouped = data.groupby(groupers, observed=False)
+ res = grouped.apply(func, *args, **kwargs)
+
+ if isinstance(res.index, pd.MultiIndex):
+ res = res.reindex(index.append(pd.Index(res.index.levels[-1])))
+ else:
+ res = res.reindex(index)
+
+ return self._reorder_columns(res, data)
diff --git a/seaborn/_core/plot.py b/seaborn/_core/plot.py
index 0695b4a6..cb54a995 100644
--- a/seaborn/_core/plot.py
+++ b/seaborn/_core/plot.py
@@ -69,7 +69,12 @@ class PairSpec(TypedDict, total=(False)):
@contextmanager
def theme_context(params: dict[str, Any]) ->Generator:
"""Temporarily modify specifc matplotlib rcParams."""
- pass
+ original_params = {k: mpl.rcParams[k] for k in params}
+ mpl.rcParams.update(params)
+ try:
+ yield
+ finally:
+ mpl.rcParams.update(original_params)
def build_plot_signature(cls):
@@ -81,7 +86,15 @@ def build_plot_signature(cls):
at which point dynamic signature generation would become more important.
"""
- pass
+ def wrapper(cls):
+ params = [
+ inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD),
+ inspect.Parameter("data", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=None),
+ *[inspect.Parameter(prop, inspect.Parameter.KEYWORD_ONLY) for prop in PROPERTIES]
+ ]
+ cls.__signature__ = inspect.Signature(params)
+ return cls
+ return wrapper
class ThemeConfig(mpl.RcParams):
@@ -98,15 +111,24 @@ class ThemeConfig(mpl.RcParams):
def reset(self) ->None:
"""Update the theme dictionary with seaborn's default values."""
- pass
+ self.clear()
+ with axes_style() as style, plotting_context() as context:
+ self.update(style)
+ self.update(context)
def update(self, other: (dict[str, Any] | None)=None, /, **kwds):
"""Update the theme with a dictionary or keyword arguments of rc parameters."""
- pass
+ if other is not None:
+ kwds.update(other)
+ filtered = self._filter_params(kwds)
+ super().update(filtered)
def _filter_params(self, params: dict[str, Any]) ->dict[str, Any]:
- """Restruct to thematic rc params."""
- pass
+ """Restrict to thematic rc params."""
+ return {
+ k: v for k, v in params.items()
+ if any(k.startswith(f"{group}.") for group in self.THEME_GROUPS)
+ }
class DisplayConfig(TypedDict):
@@ -132,7 +154,7 @@ class PlotConfig:
https://matplotlib.org/stable/tutorials/introductory/customizing.html
"""
- pass
+ return dict(self._theme)
@property
def display(self) ->DisplayConfig:
@@ -146,7 +168,7 @@ class PlotConfig:
- hidpi (bool): When True, double the DPI while preserving the size
"""
- pass
+ return self._display.copy()
@build_plot_signature
diff --git a/seaborn/_core/properties.py b/seaborn/_core/properties.py
index 74be300e..b9137f0d 100644
--- a/seaborn/_core/properties.py
+++ b/seaborn/_core/properties.py
@@ -36,27 +36,48 @@ class Property:
def default_scale(self, data: Series) ->Scale:
"""Given data, initialize appropriate scale class."""
- pass
+ data_type = variable_type(data)
+ if data_type == "numeric":
+ return Continuous()
+ elif data_type == "datetime":
+ return Temporal()
+ else:
+ return Nominal()
def infer_scale(self, arg: Any, data: Series) ->Scale:
"""Given data and a scaling argument, initialize appropriate scale class."""
- pass
+ if isinstance(arg, Scale):
+ return arg
+ elif arg is None:
+ return self.default_scale(data)
+ elif isinstance(arg, str):
+ if arg.lower() in ["categorical", "nominal"]:
+ return Nominal()
+ elif arg.lower() in ["numeric", "continuous"]:
+ return Continuous()
+ elif arg.lower() == "datetime":
+ return Temporal()
+ raise ValueError(f"Unrecognized scale type: {arg}")
def get_mapping(self, scale: Scale, data: Series) ->Mapping:
"""Return a function that maps from data domain to property range."""
- pass
+ return scale.get_mapping(data)
def standardize(self, val: Any) ->Any:
"""Coerce flexible property value to standardized representation."""
- pass
+ return val
def _check_dict_entries(self, levels: list, values: dict) ->None:
"""Input check when values are provided as a dictionary."""
- pass
+ missing = set(levels) - set(values)
+ if missing:
+ raise ValueError(f"Missing values for levels: {', '.join(missing)}")
def _check_list_length(self, levels: list, values: list) ->list:
"""Input check when values are provided as a list."""
- pass
+ if len(values) < len(levels):
+ raise ValueError(f"Not enough values ({len(values)}) for levels ({len(levels)})")
+ return values[:len(levels)]
class Coordinate(Property):
@@ -74,35 +95,54 @@ class IntervalProperty(Property):
@property
def default_range(self) ->tuple[float, float]:
"""Min and max values used by default for semantic mapping."""
- pass
+ return self._default_range
def _forward(self, values: ArrayLike) ->ArrayLike:
"""Transform applied to native values before linear mapping into interval."""
- pass
+ return values
def _inverse(self, values: ArrayLike) ->ArrayLike:
"""Transform applied to results of mapping that returns to native values."""
- pass
+ return values
def infer_scale(self, arg: Any, data: Series) ->Scale:
"""Given data and a scaling argument, initialize appropriate scale class."""
- pass
+ scale = super().infer_scale(arg, data)
+ if isinstance(scale, Continuous):
+ scale.range = self.default_range
+ return scale
def get_mapping(self, scale: Scale, data: Series) ->Mapping:
"""Return a function that maps from data domain to property range."""
- pass
+ if isinstance(scale, Nominal):
+ return self._get_nominal_mapping(scale, data)
+ elif isinstance(scale, Boolean):
+ return self._get_boolean_mapping(scale, data)
+ else:
+ return lambda x: np.interp(self._forward(x), (data.min(), data.max()), self.default_range)
def _get_nominal_mapping(self, scale: Nominal, data: Series) ->Mapping:
"""Identify evenly-spaced values using interval or explicit mapping."""
- pass
+ levels = categorical_order(data)
+ values = self._get_values(scale, levels)
+ return lambda x: dict(zip(levels, values))[x]
def _get_boolean_mapping(self, scale: Boolean, data: Series) ->Mapping:
"""Identify evenly-spaced values using interval or explicit mapping."""
- pass
+ values = self._get_values(scale, [False, True])
+ return lambda x: values[int(x)]
def _get_values(self, scale: Scale, levels: list) ->list:
"""Validate scale.values and identify a value for each level."""
- pass
+ if scale.values is None:
+ n = len(levels)
+ values = np.linspace(*self.default_range, n)
+ elif isinstance(scale.values, dict):
+ self._check_dict_entries(levels, scale.values)
+ values = [scale.values[level] for level in levels]
+ else:
+ values = self._check_list_length(levels, scale.values)
+ return list(self._inverse(values))
class PointSize(IntervalProperty):
@@ -111,11 +151,11 @@ class PointSize(IntervalProperty):
def _forward(self, values):
"""Square native values to implement linear scaling of point area."""
- pass
+ return np.square(values)
def _inverse(self, values):
"""Invert areal values back to point diameter."""
- pass
+ return np.sqrt(values)
class LineWidth(IntervalProperty):
@@ -124,7 +164,7 @@ class LineWidth(IntervalProperty):
@property
def default_range(self) ->tuple[float, float]:
"""Min and max values used by default for semantic mapping."""
- pass
+ return 0.5, 2
class EdgeWidth(IntervalProperty):
@@ -133,7 +173,7 @@ class EdgeWidth(IntervalProperty):
@property
def default_range(self) ->tuple[float, float]:
"""Min and max values used by default for semantic mapping."""
- pass
+ return 0, 2
class Stroke(IntervalProperty):
@@ -159,7 +199,7 @@ class FontSize(IntervalProperty):
@property
def default_range(self) ->tuple[float, float]:
"""Min and max values used by default for semantic mapping."""
- pass
+ return 8, 12
class ObjectProperty(Property):
@@ -170,11 +210,20 @@ class ObjectProperty(Property):
def get_mapping(self, scale: Scale, data: Series) ->Mapping:
"""Define mapping as lookup into list of object values."""
- pass
+ levels = categorical_order(data)
+ values = self._get_values(scale, levels)
+ return lambda x: dict(zip(levels, values)).get(x, self.null_value)
def _get_values(self, scale: Scale, levels: list) ->list:
"""Validate scale.values and identify a value for each level."""
- pass
+ if scale.values is None:
+ values = self._default_values(len(levels))
+ elif isinstance(scale.values, dict):
+ self._check_dict_entries(levels, scale.values)
+ values = [scale.values[level] for level in levels]
+ else:
+ values = self._check_list_length(levels, scale.values)
+ return values
class Marker(ObjectProperty):
@@ -196,7 +245,11 @@ class Marker(ObjectProperty):
All markers will be filled.
"""
- pass
+ markers = ['o', 's', 'D', '^', 'v', '<', '>', 'p', 'h', '8']
+ if n <= len(markers):
+ return [MarkerStyle(m) for m in markers[:n]]
+ else:
+ return [MarkerStyle(m) for m in markers + [f'${i}$' for i in range(n - len(markers))]]
class LineStyle(ObjectProperty):
@@ -220,12 +273,24 @@ class LineStyle(ObjectProperty):
dashes.
"""
- pass
+ dashes = ["", (4, 1.5), (1, 1),
+ (3, 1, 1.5, 1), (5, 1, 1, 1),
+ (5, 1, 2, 1, 2, 1), (2, 2, 3, 1.5),
+ (1, 2.5, 3, 1.5), (4, 1, 2, 1, 2, 1)]
+ if n <= len(dashes):
+ return [self._get_dash_pattern(d) for d in dashes[:n]]
+ else:
+ return [self._get_dash_pattern(d) for d in dashes + [(1, 1)] * (n - len(dashes))]
@staticmethod
def _get_dash_pattern(style: (str | DashPattern)) ->DashPatternWithOffset:
"""Convert linestyle arguments to dash pattern with offset."""
- pass
+ if isinstance(style, str):
+ return mpl.lines._get_dash_pattern(style)
+ elif isinstance(style, tuple):
+ return (0, style)
+ else:
+ raise ValueError(f"Unrecognized linestyle: {style}")
class TextAlignment(ObjectProperty):
@@ -247,15 +312,40 @@ class Color(Property):
def _standardize_color_sequence(self, colors: ArrayLike) ->ArrayLike:
"""Convert color sequence to RGB(A) array, preserving but not adding alpha."""
- pass
+ rgba = to_rgba_array(colors)
+ if np.all(rgba[:, 3] == 1):
+ return rgba[:, :3]
+ return rgba
def get_mapping(self, scale: Scale, data: Series) ->Mapping:
"""Return a function that maps from data domain to color values."""
- pass
+ if isinstance(scale, Nominal):
+ levels = categorical_order(data)
+ palette = self._get_values(scale, levels)
+ color_list = self._standardize_color_sequence(palette)
+ return lambda x: dict(zip(levels, color_list))[x]
+ elif isinstance(scale, Boolean):
+ palette = self._get_values(scale, [False, True])
+ color_list = self._standardize_color_sequence(palette)
+ return lambda x: color_list[int(x)]
+ else:
+ palette = self._get_values(scale, [])
+ return lambda x: self._standardize_color_sequence(
+ blend_palette(palette, x, as_cmap=True)(x)
+ )
def _get_values(self, scale: Scale, levels: list) ->ArrayLike:
"""Validate scale.values and identify a value for each level."""
- pass
+ if scale.values is None:
+ n_colors = len(levels) if levels else 256
+ return color_palette(n_colors=n_colors)
+ elif isinstance(scale.values, str):
+ return color_palette(scale.values, n_colors=len(levels))
+ elif isinstance(scale.values, dict):
+ self._check_dict_entries(levels, scale.values)
+ return [scale.values[level] for level in levels]
+ else:
+ return self._check_list_length(levels, scale.values)
class Fill(Property):
@@ -265,15 +355,29 @@ class Fill(Property):
def _default_values(self, n: int) ->list:
"""Return a list of n values, alternating True and False."""
- pass
+ return [i % 2 == 0 for i in range(n)]
def get_mapping(self, scale: Scale, data: Series) ->Mapping:
"""Return a function that maps each data value to True or False."""
- pass
+ if isinstance(scale, Nominal):
+ levels = categorical_order(data)
+ values = self._get_values(scale, levels)
+ return lambda x: dict(zip(levels, values))[x]
+ elif isinstance(scale, Boolean):
+ values = self._get_values(scale, [False, True])
+ return lambda x: values[int(x)]
+ else:
+ raise ValueError("Fill property only supports nominal or boolean scales")
def _get_values(self, scale: Scale, levels: list) ->list:
"""Validate scale.values and identify a value for each level."""
- pass
+ if scale.values is None:
+ return self._default_values(len(levels))
+ elif isinstance(scale.values, dict):
+ self._check_dict_entries(levels, scale.values)
+ return [scale.values[level] for level in levels]
+ else:
+ return self._check_list_length(levels, scale.values)
PROPERTY_CLASSES = {'x': Coordinate, 'y': Coordinate, 'color': Color,
diff --git a/seaborn/_core/rules.py b/seaborn/_core/rules.py
index d78093c0..a6be865e 100644
--- a/seaborn/_core/rules.py
+++ b/seaborn/_core/rules.py
@@ -57,7 +57,32 @@ def variable_type(vector: Series, boolean_type: Literal['numeric',
var_type : 'numeric', 'categorical', or 'datetime'
Name identifying the type of data in the vector.
"""
- pass
+ if not isinstance(vector, pd.Series):
+ vector = pd.Series(vector)
+
+ if pd.api.types.is_datetime64_any_dtype(vector):
+ return VarType('datetime')
+
+ if strict_boolean and pd.api.types.is_bool_dtype(vector):
+ return VarType(boolean_type)
+
+ if pd.api.types.is_numeric_dtype(vector):
+ if set(vector.dropna().unique()) <= {0, 1}:
+ return VarType(boolean_type)
+ return VarType('numeric')
+
+ if pd.api.types.is_categorical_dtype(vector):
+ return VarType('categorical')
+
+ # Check if all values are numeric
+ try:
+ pd.to_numeric(vector, errors='raise')
+ return VarType('numeric')
+ except (ValueError, TypeError):
+ pass
+
+ # If we reach here, it's likely categorical
+ return VarType('categorical')
def categorical_order(vector: Series, order: (list | None)=None) ->list:
@@ -78,4 +103,16 @@ def categorical_order(vector: Series, order: (list | None)=None) ->list:
Ordered list of category levels not including null values.
"""
- pass
+ if order is not None:
+ return [c for c in order if c in vector.unique()]
+
+ if hasattr(vector, "categories"):
+ return [c for c in vector.categories if c in vector.unique()]
+
+ if vector.dtype.name == "category":
+ return [c for c in vector.cat.categories if c in vector.unique()]
+
+ if pd.api.types.is_numeric_dtype(vector):
+ return sorted(vector.unique())
+
+ return pd.Series(vector).unique().tolist()
diff --git a/seaborn/_core/scales.py b/seaborn/_core/scales.py
index 99f98988..4b3d09ab 100644
--- a/seaborn/_core/scales.py
+++ b/seaborn/_core/scales.py
@@ -101,7 +101,9 @@ class Nominal(Scale):
Copy of self with new tick configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._tick_params = {"locator": locator}
+ return new_scale
def label(self, formatter: (Formatter | None)=None) ->Nominal:
"""
@@ -122,7 +124,9 @@ class Nominal(Scale):
Copy of self with new tick configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._label_params = {"formatter": formatter}
+ return new_scale
@dataclass
@@ -180,7 +184,17 @@ class Continuous(ContinuousBase):
Copy of self with new tick configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._tick_params = {
+ "locator": locator,
+ "at": at,
+ "upto": upto,
+ "count": count,
+ "every": every,
+ "between": between,
+ "minor": minor
+ }
+ return new_scale
def label(self, formatter: (Formatter | None)=None, *, like: (str |
Callable | None)=None, base: (int | None | Default)=default, unit:
@@ -211,7 +225,14 @@ class Continuous(ContinuousBase):
Copy of self with new label configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._label_params = {
+ "formatter": formatter,
+ "like": like,
+ "base": base,
+ "unit": unit
+ }
+ return new_scale
@dataclass
@@ -243,7 +264,12 @@ class Temporal(ContinuousBase):
Copy of self with new tick configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._tick_params = {
+ "locator": locator,
+ "upto": upto
+ }
+ return new_scale
def label(self, formatter: (Formatter | None)=None, *, concise: bool=False
) ->Temporal:
@@ -267,7 +293,12 @@ class Temporal(ContinuousBase):
Copy of self with new label configuration.
"""
- pass
+ new_scale = copy(self)
+ new_scale._label_params = {
+ "formatter": formatter,
+ "concise": concise
+ }
+ return new_scale
class PseudoAxis:
diff --git a/seaborn/_core/subplots.py b/seaborn/_core/subplots.py
index ab72e2f0..3263459e 100644
--- a/seaborn/_core/subplots.py
+++ b/seaborn/_core/subplots.py
@@ -39,27 +39,71 @@ class Subplots:
def _check_dimension_uniqueness(self, facet_spec: FacetSpec, pair_spec:
PairSpec) ->None:
"""Reject specs that pair and facet on (or wrap to) same figure dimension."""
- pass
+ facet_dims = set(facet_spec.get("col", "")) | set(facet_spec.get("row", ""))
+ pair_dims = set(pair_spec.get("x", "")) | set(pair_spec.get("y", ""))
+
+ if facet_dims & pair_dims:
+ raise ValueError("Cannot facet and pair on the same dimension.")
+
+ if facet_spec.get("wrap") and (facet_spec.get("wrap") in pair_dims):
+ raise ValueError("Cannot wrap facets on a dimension used for pairing.")
def _determine_grid_dimensions(self, facet_spec: FacetSpec, pair_spec:
PairSpec) ->None:
"""Parse faceting and pairing information to define figure structure."""
- pass
+ self.nrows = facet_spec.get("row", 1)
+ self.ncols = facet_spec.get("col", 1)
+
+ if pair_spec:
+ self.nrows *= len(pair_spec.get("y", [1]))
+ self.ncols *= len(pair_spec.get("x", [1]))
+
+ self.n_subplots = self.nrows * self.ncols
def _handle_wrapping(self, facet_spec: FacetSpec, pair_spec: PairSpec
) ->None:
"""Update figure structure parameters based on facet/pair wrapping."""
- pass
+ wrap = facet_spec.get("wrap")
+ if wrap:
+ total_subplots = self.nrows * self.ncols
+ self.ncols = min(wrap, total_subplots)
+ self.nrows = (total_subplots - 1) // self.ncols + 1
def _determine_axis_sharing(self, pair_spec: PairSpec) ->None:
"""Update subplot spec with default or specified axis sharing parameters."""
- pass
+ self.subplot_spec.setdefault("sharex", "col" in pair_spec)
+ self.subplot_spec.setdefault("sharey", "row" in pair_spec)
def init_figure(self, pair_spec: PairSpec, pyplot: bool=False,
figure_kws: (dict | None)=None, target: (Axes | Figure | SubFigure |
None)=None) ->Figure:
"""Initialize matplotlib objects and add seaborn-relevant metadata."""
- pass
+ if target is None:
+ if pyplot:
+ fig, axes = plt.subplots(self.nrows, self.ncols, **self.subplot_spec)
+ else:
+ fig = Figure(**figure_kws or {})
+ axes = fig.subplots(self.nrows, self.ncols, **self.subplot_spec)
+ else:
+ if isinstance(target, Axes):
+ fig = target.figure
+ axes = np.array([[target]])
+ elif isinstance(target, (Figure, SubFigure)):
+ fig = target
+ axes = fig.subplots(self.nrows, self.ncols, **self.subplot_spec)
+ else:
+ raise TypeError("Unsupported target type")
+
+ self._subplot_list = [
+ {
+ "ax": ax,
+ "row": i // self.ncols,
+ "col": i % self.ncols,
+ }
+ for i, ax in enumerate(axes.flat)
+ ]
+
+ return fig
def __iter__(self) ->Generator[dict, None, None]:
"""Yield each subplot dictionary with Axes object and metadata."""
diff --git a/seaborn/_docstrings.py b/seaborn/_docstrings.py
index 92bca3b0..6d90fb35 100644
--- a/seaborn/_docstrings.py
+++ b/seaborn/_docstrings.py
@@ -36,12 +36,25 @@ class DocstringComponents:
@classmethod
def from_nested_components(cls, **kwargs):
"""Add multiple sub-sets of components."""
- pass
+ entries = {}
+ for key, value in kwargs.items():
+ if isinstance(value, cls):
+ entries.update(value.entries)
+ elif isinstance(value, dict):
+ entries.update(value)
+ else:
+ raise ValueError(f"Invalid component type for {key}")
+ return cls(entries)
@classmethod
def from_function_params(cls, func):
"""Use the numpydoc parser to extract components from existing func."""
- pass
+ doc = NumpyDocString(pydoc.getdoc(func))
+ params = {}
+ for param in doc['Parameters']:
+ name, _, desc = param
+ params[name] = '\n'.join(desc)
+ return cls(params)
_core_params = dict(data=
diff --git a/seaborn/_marks/area.py b/seaborn/_marks/area.py
index 427c1a16..bb3181cd 100644
--- a/seaborn/_marks/area.py
+++ b/seaborn/_marks/area.py
@@ -7,7 +7,30 @@ from seaborn._marks.base import Mark, Mappable, MappableBool, MappableFloat, Map
class AreaBase:
- pass
+ def __init__(self):
+ self._artist = None
+
+ def _plot(self, ax, x, y, y2=None, **kwargs):
+ if y2 is None:
+ y2 = self.baseline
+
+ fill_between_kwargs = {
+ 'x': x,
+ 'y1': y,
+ 'y2': y2,
+ 'color': kwargs.get('color'),
+ 'alpha': kwargs.get('alpha'),
+ 'edgecolor': kwargs.get('edgecolor'),
+ 'linewidth': kwargs.get('edgewidth'),
+ 'linestyle': kwargs.get('edgestyle'),
+ }
+
+ self._artist = ax.fill_between(**fill_between_kwargs)
+ return self._artist
+
+ def _legend_artist(self, color):
+ from matplotlib.patches import Rectangle
+ return Rectangle((0, 0), 1, 1, facecolor=color, edgecolor=None)
@document_properties
@@ -34,6 +57,22 @@ class Area(AreaBase, Mark):
edgestyle: MappableStyle = Mappable('-')
baseline: MappableFloat = Mappable(0, grouping=False)
+ def __post_init__(self):
+ super().__init__()
+
+ def plot(self, ax, x, y, **kwargs):
+ properties = resolve_properties(self, kwargs)
+ if not properties['fill']:
+ properties['alpha'] = 0
+
+ color = resolve_color(properties.pop('color'))
+ properties['color'] = color
+
+ edgecolor = resolve_color(properties.pop('edgecolor'), color)
+ properties['edgecolor'] = edgecolor
+
+ return self._plot(ax, x, y, **properties)
+
@document_properties
@dataclass
@@ -57,3 +96,19 @@ class Band(AreaBase, Mark):
edgealpha: MappableFloat = Mappable(1)
edgewidth: MappableFloat = Mappable(0)
edgestyle: MappableFloat = Mappable('-')
+
+ def __post_init__(self):
+ super().__init__()
+
+ def plot(self, ax, x, y1, y2, **kwargs):
+ properties = resolve_properties(self, kwargs)
+ if not properties['fill']:
+ properties['alpha'] = 0
+
+ color = resolve_color(properties.pop('color'))
+ properties['color'] = color
+
+ edgecolor = resolve_color(properties.pop('edgecolor'), color)
+ properties['edgecolor'] = edgecolor
+
+ return self._plot(ax, x, y1, y2, **properties)
diff --git a/seaborn/_marks/base.py b/seaborn/_marks/base.py
index 03ee03a9..d643a8c5 100644
--- a/seaborn/_marks/base.py
+++ b/seaborn/_marks/base.py
@@ -62,12 +62,19 @@ class Mappable:
@property
def depend(self) ->Any:
"""Return the name of the feature to source a default value from."""
- pass
+ return self._depend
@property
def default(self) ->Any:
"""Get the default value for this feature, or access the relevant rcParam."""
- pass
+ if self._val is not None:
+ return self._val
+ elif self._rc is not None:
+ return mpl.rcParams[self._rc]
+ elif self._auto:
+ return None # This will be handled at compile time
+ else:
+ return None
MappableBool = Union[bool, Mappable]
@@ -103,12 +110,33 @@ class Mark:
of values with matching length).
"""
- pass
+ feature = getattr(self, name)
+
+ if isinstance(feature, Mappable):
+ if feature.depend is not None:
+ return self._resolve(data, feature.depend, scales)
+ elif name in data:
+ if isinstance(data, dict):
+ return data[name]
+ elif isinstance(data, DataFrame):
+ if scales and name in scales:
+ return scales[name].transform(data[name])
+ else:
+ return data[name].to_numpy()
+ else:
+ return feature.default
+ else:
+ return feature
def _plot(self, split_generator: Callable[[], Generator], scales: dict[
str, Scale], orient: str) ->None:
"""Main interface for creating a plot."""
- pass
+ for split_data in split_generator():
+ self._draw(split_data, scales, orient)
+
+ def _draw(self, data: DataFrame, scales: dict[str, Scale], orient: str) ->None:
+ """Abstract method to be implemented by subclasses for actual drawing."""
+ raise NotImplementedError("Subclasses must implement _draw method")
def resolve_color(mark: Mark, data: (DataFrame | dict), prefix: str='',
@@ -133,4 +161,21 @@ def resolve_color(mark: Mark, data: (DataFrame | dict), prefix: str='',
Support "color", "fillcolor", etc.
"""
- pass
+ color_name = f"{prefix}color" if prefix else "color"
+ alpha_name = f"{prefix}alpha" if prefix else "alpha"
+
+ color = mark._resolve(data, color_name, scales)
+ alpha = mark._resolve(data, alpha_name, scales)
+
+ if isinstance(data, dict):
+ return _apply_alpha_to_color(color, alpha)
+ elif isinstance(data, DataFrame):
+ return np.array([_apply_alpha_to_color(c, a) for c, a in zip(color, alpha)])
+
+def _apply_alpha_to_color(color: Any, alpha: float) ->RGBATuple:
+ """Helper function to apply alpha to a color."""
+ try:
+ c = mpl.colors.to_rgba(color)
+ return c[:3] + (alpha if alpha is not None else c[3],)
+ except ValueError:
+ raise PlotSpecError(f"Invalid color specification: {color}")
diff --git a/seaborn/_statistics.py b/seaborn/_statistics.py
index 2b81faf7..ecbeb933 100644
--- a/seaborn/_statistics.py
+++ b/seaborn/_statistics.py
@@ -79,31 +79,66 @@ class KDE:
def _define_support_grid(self, x, bw, cut, clip, gridsize):
"""Create the grid of evaluation points depending for vector x."""
- pass
+ x = np.asarray(x)
+ support_min = x.min() - bw * cut
+ support_max = x.max() + bw * cut
+ if clip is not None:
+ support_min = max(support_min, clip[0])
+ support_max = min(support_max, clip[1])
+ return np.linspace(support_min, support_max, gridsize)
def _define_support_univariate(self, x, weights):
"""Create a 1D grid of evaluation points."""
- pass
+ kde = self._fit(x, weights)
+ bw = kde.factor * np.std(x, ddof=1)
+ return self._define_support_grid(x, bw, self.cut, self.clip, self.gridsize)
def _define_support_bivariate(self, x1, x2, weights):
"""Create a 2D grid of evaluation points."""
- pass
+ kde = self._fit(np.c_[x1, x2], weights)
+ bw = kde.factor * np.std([x1, x2], axis=1, ddof=1)
+ grid1 = self._define_support_grid(x1, bw[0], self.cut, self.clip[0], self.gridsize)
+ grid2 = self._define_support_grid(x2, bw[1], self.cut, self.clip[1], self.gridsize)
+ return np.meshgrid(grid1, grid2)
def define_support(self, x1, x2=None, weights=None, cache=True):
"""Create the evaluation grid for a given data set."""
- pass
+ if x2 is None:
+ support = self._define_support_univariate(x1, weights)
+ else:
+ support = self._define_support_bivariate(x1, x2, weights)
+
+ if cache:
+ self.support = support
+ return support
def _fit(self, fit_data, weights=None):
"""Fit the scipy kde while adding bw_adjust logic and version check."""
- pass
+ kde = gaussian_kde(fit_data.T, weights=weights, bw_method=self.bw_method)
+ kde.set_bandwidth(kde.factor * self.bw_adjust)
+ return kde
def _eval_univariate(self, x, weights=None):
"""Fit and evaluate a univariate on univariate data."""
- pass
+ kde = self._fit(x[:, np.newaxis], weights)
+ if self.support is None:
+ self.define_support(x, weights=weights)
+ density = kde(self.support)
+ if self.cumulative:
+ density = np.cumsum(density) / density.sum()
+ return self.support, density
def _eval_bivariate(self, x1, x2, weights=None):
"""Fit and evaluate a univariate on bivariate data."""
- pass
+ kde = self._fit(np.c_[x1, x2], weights)
+ if self.support is None:
+ self.define_support(x1, x2, weights=weights)
+ density = kde(np.vstack([self.support[0].ravel(), self.support[1].ravel()]))
+ density = density.reshape(self.support[0].shape)
+ if self.cumulative:
+ density = np.cumsum(density.ravel()).reshape(density.shape)
+ density /= density.max()
+ return self.support, density
def __call__(self, x1, x2=None, weights=None):
"""Fit and evaluate on univariate or bivariate data."""
@@ -159,22 +194,75 @@ class Histogram:
self.cumulative = cumulative
self.bin_kws = None
- def _define_bin_edges(self, x, weights, bins, binwidth, binrange, discrete
- ):
+ def _define_bin_edges(self, x, weights, bins, binwidth, binrange, discrete):
"""Inner function that takes bin parameters as arguments."""
- pass
+ if discrete:
+ bins = np.arange(x.min() - 0.5, x.max() + 1.5)
+ elif binwidth is not None:
+ bins = np.arange(binrange[0], binrange[1] + binwidth, binwidth)
+ else:
+ bins = np.histogram_bin_edges(x, bins, binrange, weights)
+ return bins
def define_bin_params(self, x1, x2=None, weights=None, cache=True):
"""Given data, return numpy.histogram parameters to define bins."""
- pass
+ if x2 is None:
+ bin_edges = self._define_bin_edges(x1, weights, self.bins, self.binwidth, self.binrange, self.discrete)
+ bin_kws = {"bins": bin_edges}
+ else:
+ bin_edges1 = self._define_bin_edges(x1, weights, self.bins, self.binwidth, self.binrange[0], self.discrete[0])
+ bin_edges2 = self._define_bin_edges(x2, weights, self.bins, self.binwidth, self.binrange[1], self.discrete[1])
+ bin_kws = {"bins": [bin_edges1, bin_edges2]}
+
+ if cache:
+ self.bin_kws = bin_kws
+ return bin_kws
def _eval_bivariate(self, x1, x2, weights):
"""Inner function for histogram of two variables."""
- pass
+ if self.bin_kws is None:
+ self.define_bin_params(x1, x2, weights)
+
+ hist, _, _ = np.histogram2d(x1, x2, weights=weights, **self.bin_kws)
+
+ if self.stat == "density":
+ hist /= hist.sum() * np.diff(self.bin_kws["bins"][0]).mean() * np.diff(self.bin_kws["bins"][1]).mean()
+ elif self.stat in ["probability", "proportion"]:
+ hist /= hist.sum()
+ elif self.stat == "percent":
+ hist /= hist.sum() / 100
+ elif self.stat == "frequency":
+ hist /= np.diff(self.bin_kws["bins"][0]).mean() * np.diff(self.bin_kws["bins"][1]).mean()
+
+ if self.cumulative:
+ hist = np.cumsum(hist)
+ if self.stat in ["probability", "proportion", "percent"]:
+ hist /= hist[-1, -1]
+
+ return self.bin_kws["bins"][0], self.bin_kws["bins"][1], hist
def _eval_univariate(self, x, weights):
"""Inner function for histogram of one variable."""
- pass
+ if self.bin_kws is None:
+ self.define_bin_params(x, weights=weights)
+
+ hist, bin_edges = np.histogram(x, weights=weights, **self.bin_kws)
+
+ if self.stat == "density":
+ hist = hist / (hist.sum() * np.diff(bin_edges))
+ elif self.stat in ["probability", "proportion"]:
+ hist = hist / hist.sum()
+ elif self.stat == "percent":
+ hist = hist / hist.sum() * 100
+ elif self.stat == "frequency":
+ hist = hist / np.diff(bin_edges)
+
+ if self.cumulative:
+ hist = np.cumsum(hist)
+ if self.stat in ["probability", "proportion", "percent"]:
+ hist /= hist[-1]
+
+ return bin_edges, hist
def __call__(self, x1, x2=None, weights=None):
"""Count the occurrences in each bin, maybe normalize."""
@@ -204,11 +292,28 @@ class ECDF:
def _eval_bivariate(self, x1, x2, weights):
"""Inner function for ECDF of two variables."""
- pass
+ raise NotImplementedError("Bivariate ECDF is not implemented.")
def _eval_univariate(self, x, weights):
"""Inner function for ECDF of one variable."""
- pass
+ sorter = np.argsort(x)
+ x = x[sorter]
+ weights = weights[sorter]
+
+ cumulative_weights = np.cumsum(weights)
+ total_weight = cumulative_weights[-1]
+
+ if self.stat == "count":
+ y = cumulative_weights
+ elif self.stat == "proportion":
+ y = cumulative_weights / total_weight
+ elif self.stat == "percent":
+ y = 100 * cumulative_weights / total_weight
+
+ if self.complementary:
+ y = total_weight - y if self.stat == "count" else 1 - y
+
+ return x, y
def __call__(self, x1, x2=None, weights=None):
"""Return proportion or count of observations below each sorted datapoint."""
@@ -379,12 +484,39 @@ class LetterValues:
return {'k': k, 'levels': levels, 'percs': percentiles, 'values':
values, 'fliers': fliers, 'median': median}
+ def _compute_k(self, n):
+ """Compute the number of letter values to use."""
+ if isinstance(self.k_depth, int):
+ return min(self.k_depth, int(np.log2(n)))
+ elif self.k_depth == 'tukey':
+ return min(int(np.log2(n)), 3)
+ elif self.k_depth == 'proportion':
+ return min(int(np.log2(n)), max(1, int(-np.log2(2 * self.outlier_prop))))
+ elif self.k_depth == 'trustworthy':
+ return min(int(np.log2(n)), max(1, int(-np.log2(2 * self.trust_alpha / n))))
+ else: # 'full'
+ return int(np.log2(n))
+
def _percentile_interval(data, width):
"""Return a percentile interval from data of a given width."""
- pass
+ low, high = (50 - width / 2, 50 + width / 2)
+ return np.percentile(data, [low, high])
def _validate_errorbar_arg(arg):
"""Check type and value of errorbar argument and assign default level."""
- pass
+ if arg is None:
+ return None, None
+ elif isinstance(arg, str):
+ method = arg
+ level = .95 if method == "ci" else 1
+ elif isinstance(arg, tuple):
+ method, level = arg
+ elif callable(arg):
+ method = arg
+ level = None
+ else:
+ raise ValueError(f"Input {arg!r} not understood for errorbar.")
+
+ return method, level
diff --git a/seaborn/_stats/base.py b/seaborn/_stats/base.py
index 4c8201ba..3055a586 100644
--- a/seaborn/_stats/base.py
+++ b/seaborn/_stats/base.py
@@ -18,14 +18,30 @@ class Stat:
def _check_param_one_of(self, param: str, options: Iterable[Any]) ->None:
"""Raise when parameter value is not one of a specified set."""
- pass
+ if param not in options:
+ raise ValueError(f"Parameter '{param}' must be one of {options}")
def _check_grouping_vars(self, param: str, data_vars: list[str],
stacklevel: int=2) ->None:
"""Warn if vars are named in parameter without being present in the data."""
- pass
+ missing_vars = set(param.split()) - set(data_vars)
+ if missing_vars:
+ warnings.warn(
+ f"The following variable(s) are not present in the data: {', '.join(missing_vars)}",
+ UserWarning,
+ stacklevel=stacklevel
+ )
def __call__(self, data: DataFrame, groupby: GroupBy, orient: str,
scales: dict[str, Scale]) ->DataFrame:
"""Apply statistical transform to data subgroups and return combined result."""
- return data
+ if self.group_by_orient:
+ grouped_data = groupby.apply(data, self._transform, orient, scales)
+ else:
+ grouped_data = groupby.agg(data, self._transform, orient, scales)
+ return grouped_data
+
+ def _transform(self, data: DataFrame, orient: str, scales: dict[str, Scale]) ->DataFrame:
+ """Implement the statistical transformation."""
+ # This method should be overridden in subclasses
+ raise NotImplementedError("Subclasses must implement _transform method")
diff --git a/seaborn/_stats/counting.py b/seaborn/_stats/counting.py
index b1bf2a2d..ebcd2f42 100644
--- a/seaborn/_stats/counting.py
+++ b/seaborn/_stats/counting.py
@@ -112,11 +112,45 @@ class Hist(Stat):
def _define_bin_edges(self, vals, weight, bins, binwidth, binrange,
discrete):
"""Inner function that takes bin parameters as arguments."""
- pass
+ if discrete:
+ if binrange is None:
+ start, stop = int(vals.min()), int(vals.max())
+ else:
+ start, stop = map(int, binrange)
+ return np.arange(start - 0.5, stop + 1.5)
+
+ if binwidth is not None:
+ if binrange is None:
+ start, stop = vals.min(), vals.max()
+ else:
+ start, stop = binrange
+ return np.arange(start, stop + binwidth, binwidth)
+
+ if np.isscalar(bins):
+ if binrange is not None:
+ range_param = binrange
+ else:
+ range_param = (vals.min(), vals.max())
+ return np.histogram_bin_edges(vals, bins, range_param, weights=weight)
+
+ return np.asarray(bins)
def _define_bin_params(self, data, orient, scale_type):
"""Given data, return numpy.histogram parameters to define bins."""
- pass
+ vals = data[orient].to_numpy()
+ weight = data.get("weight", None)
+
+ if scale_type == "datetime":
+ vals = vals.astype(float)
+
+ bin_edges = self._define_bin_edges(
+ vals, weight, self.bins, self.binwidth, self.binrange, self.discrete
+ )
+
+ if scale_type == "datetime":
+ bin_edges = pd.to_datetime(bin_edges)
+
+ return dict(bins=bin_edges, range=(bin_edges[0], bin_edges[-1]))
def __call__(self, data: DataFrame, groupby: GroupBy, orient: str,
scales: dict[str, Scale]) ->DataFrame:
diff --git a/seaborn/_stats/density.py b/seaborn/_stats/density.py
index 410c1a0b..896aca3b 100644
--- a/seaborn/_stats/density.py
+++ b/seaborn/_stats/density.py
@@ -96,25 +96,61 @@ class KDE(Stat):
def _check_var_list_or_boolean(self, param: str, grouping_vars: Any
) ->None:
"""Do input checks on grouping parameters."""
- pass
+ if not isinstance(getattr(self, param), (bool, list)):
+ raise TypeError(f"{param} must be boolean or list of variables")
+ if isinstance(getattr(self, param), list):
+ invalid_vars = set(getattr(self, param)) - set(grouping_vars)
+ if invalid_vars:
+ raise ValueError(f"Invalid variables in {param}: {invalid_vars}")
def _fit(self, data: DataFrame, orient: str) ->gaussian_kde:
"""Fit and return a KDE object."""
- pass
+ x = data[orient].to_numpy()
+ weights = data['weight'].to_numpy()
+ kde = gaussian_kde(x, weights=weights, bw_method=self.bw_method)
+ kde.set_bandwidth(kde.factor * self.bw_adjust)
+ return kde
def _get_support(self, data: DataFrame, orient: str) ->ndarray:
"""Define the grid that the KDE will be evaluated on."""
- pass
+ x = data[orient]
+ if self.gridsize is None:
+ return x.to_numpy()
+
+ bw = self._fit(data, orient).factor
+ grid_min = x.min() - self.cut * bw
+ grid_max = x.max() + self.cut * bw
+ return np.linspace(grid_min, grid_max, self.gridsize)
def _fit_and_evaluate(self, data: DataFrame, orient: str, support: ndarray
) ->DataFrame:
"""Transform single group by fitting a KDE and evaluating on a support grid."""
- pass
+ kde = self._fit(data, orient)
+ density = kde(support)
+
+ if self.cumulative:
+ density = np.cumsum(density) / np.sum(density)
+
+ return pd.DataFrame({orient: support, 'density': density})
def _transform(self, data: DataFrame, orient: str, grouping_vars: list[str]
) ->DataFrame:
"""Transform multiple groups by fitting KDEs and evaluating."""
- pass
+ support = self._get_support(data, orient)
+
+ if not grouping_vars:
+ return self._fit_and_evaluate(data, orient, support)
+
+ groups = data.groupby(grouping_vars)
+ results = []
+
+ for _, group_data in groups:
+ group_result = self._fit_and_evaluate(group_data, orient, support)
+ for var, val in zip(grouping_vars, group_data[grouping_vars].iloc[0]):
+ group_result[var] = val
+ results.append(group_result)
+
+ return pd.concat(results, ignore_index=True)
def __call__(self, data: DataFrame, groupby: GroupBy, orient: str,
scales: dict[str, Scale]) ->DataFrame:
diff --git a/seaborn/algorithms.py b/seaborn/algorithms.py
index 2939e8bd..0ecef9ae 100644
--- a/seaborn/algorithms.py
+++ b/seaborn/algorithms.py
@@ -32,9 +32,51 @@ def bootstrap(*args, **kwargs):
array of bootstrapped statistic values
"""
- pass
+ n_boot = kwargs.get('n_boot', 10000)
+ axis = kwargs.get('axis', None)
+ units = kwargs.get('units', None)
+ func = kwargs.get('func', 'mean')
+ seed = kwargs.get('seed', None)
+ if isinstance(func, str):
+ if func.startswith('nan'):
+ func = getattr(np, func)
+ else:
+ func = getattr(np, func)
+ if np.any([np.isnan(arg).any() for arg in args]):
+ func = getattr(np, f'nan{func}')
-def _structured_bootstrap(args, n_boot, units, func, func_kwargs, integers):
+ if axis is not None:
+ kwargs['axis'] = axis
+
+ if units is not None:
+ return _structured_bootstrap(args, n_boot, units, func, kwargs, seed)
+
+ rng = np.random.default_rng(seed)
+ boot_dist = []
+ for _ in range(n_boot):
+ resampled = [rng.choice(arg, size=len(arg), replace=True) for arg in args]
+ boot_dist.append(func(*resampled, **kwargs))
+
+ return np.array(boot_dist)
+
+
+def _structured_bootstrap(args, n_boot, units, func, func_kwargs, seed):
"""Resample units instead of datapoints."""
- pass
+ unique_units = np.unique(units)
+ n_units = len(unique_units)
+
+ rng = np.random.default_rng(seed)
+ boot_dist = []
+
+ for _ in range(n_boot):
+ resampled_units = rng.choice(unique_units, size=n_units, replace=True)
+ resampled_data = []
+
+ for arg in args:
+ resampled = np.concatenate([arg[units == unit] for unit in resampled_units])
+ resampled_data.append(resampled)
+
+ boot_dist.append(func(*resampled_data, **func_kwargs))
+
+ return np.array(boot_dist)
diff --git a/seaborn/axisgrid.py b/seaborn/axisgrid.py
index 54c0052d..cfc82ce7 100644
--- a/seaborn/axisgrid.py
+++ b/seaborn/axisgrid.py
@@ -24,17 +24,21 @@ class _BaseGrid:
def set(self, **kwargs):
"""Set attributes on each subplot Axes."""
- pass
+ for ax in self.axes.flat:
+ ax.set(**kwargs)
+ return self
@property
def fig(self):
"""DEPRECATED: prefer the `figure` property."""
- pass
+ import warnings
+ warnings.warn("The `fig` property is deprecated. Use `figure` instead.", DeprecationWarning)
+ return self.figure
@property
def figure(self):
"""Access the :class:`matplotlib.figure.Figure` object underlying the grid."""
- pass
+ return self._figure
def apply(self, func, *args, **kwargs):
"""
@@ -48,7 +52,8 @@ class _BaseGrid:
Added in v0.12.0.
"""
- pass
+ func(self, *args, **kwargs)
+ return self
def pipe(self, func, *args, **kwargs):
"""
@@ -62,7 +67,7 @@ class _BaseGrid:
Added in v0.12.0.
"""
- pass
+ return func(self, *args, **kwargs)
def savefig(self, *args, **kwargs):
"""
@@ -72,7 +77,8 @@ class _BaseGrid:
by default. Parameters are passed through to the matplotlib function.
"""
- pass
+ kwargs.setdefault("bbox_inches", "tight")
+ self.figure.savefig(*args, **kwargs)
class Grid(_BaseGrid):
@@ -87,7 +93,10 @@ class Grid(_BaseGrid):
def tight_layout(self, *args, **kwargs):
"""Call fig.tight_layout within rect that exclude the legend."""
- pass
+ kwargs.setdefault("rect", self._tight_layout_rect)
+ kwargs.setdefault("pad", self._tight_layout_pad)
+ self.figure.tight_layout(*args, **kwargs)
+ return self
def add_legend(self, legend_data=None, title=None, label_order=None,
adjust_subtitles=False, **kwargs):
@@ -117,20 +126,51 @@ class Grid(_BaseGrid):
Returns self for easy chaining.
"""
- pass
+ # Use default legend data if not provided
+ if legend_data is None:
+ legend_data = self._legend_data
+
+ # Use default title if not provided
+ if title is None:
+ title = self._hue_var
+
+ # Use default label order if not provided
+ if label_order is None:
+ label_order = self.hue_names
+
+ # Create the legend
+ legend = self.figure.legend(
+ handles=[legend_data[label] for label in label_order],
+ labels=label_order,
+ title=title,
+ **kwargs
+ )
+
+ if adjust_subtitles:
+ adjust_legend_subtitles(legend)
+
+ self._legend = legend
+ return self
def _update_legend_data(self, ax):
"""Extract the legend data from an axes object and save it."""
- pass
+ handles, labels = ax.get_legend_handles_labels()
+ self._legend_data.update(dict(zip(labels, handles)))
def _get_palette(self, data, hue, hue_order, palette):
"""Get a list of colors for the hue variable."""
- pass
+ if palette is None:
+ if hue is None:
+ palette = color_palette()
+ else:
+ n_colors = len(data[hue].unique())
+ palette = color_palette(n_colors=n_colors)
+ return palette
@property
def legend(self):
"""The :class:`matplotlib.legend.Legend` object, if present."""
- pass
+ return self._legend
def tick_params(self, axis='both', **kwargs):
"""Modify the ticks, tick labels, and gridlines.
@@ -149,7 +189,9 @@ class Grid(_BaseGrid):
Returns self for easy chaining.
"""
- pass
+ for ax in self.axes.flat:
+ ax.tick_params(axis=axis, **kwargs)
+ return self
_facet_docs = dict(data=dedent(
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index b0ff7288..9a7f5b2d 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -1033,23 +1033,58 @@ class Beeswarm:
def beeswarm(self, orig_xyr):
"""Adjust x position of points to avoid overlaps."""
- pass
+ swarm = []
+ for xyr_i in orig_xyr:
+ candidates = self.position_candidates(xyr_i, self.could_overlap(xyr_i, swarm))
+ new_xyr_i = self.first_non_overlapping_candidate(candidates, swarm)
+ swarm.append(new_xyr_i)
+ return np.array(swarm)
def could_overlap(self, xyr_i, swarm):
"""Return a list of all swarm points that could overlap with target."""
- pass
+ x, y, r = xyr_i
+ neighbors = []
+ r_search = r * 2
+ for xyr_j in swarm:
+ if abs(xyr_j[1] - y) <= r_search:
+ neighbors.append(xyr_j)
+ return neighbors
def position_candidates(self, xyr_i, neighbors):
"""Return a list of coordinates that might be valid by adjusting x."""
- pass
+ x, y, r = xyr_i
+ candidates = [xyr_i]
+ if neighbors:
+ for side in [-1, 1]:
+ for neighbor in neighbors:
+ new_x = neighbor[0] + (r + neighbor[2]) * side
+ new_xyr = (new_x, y, r)
+ candidates.append(new_xyr)
+ return candidates
def first_non_overlapping_candidate(self, candidates, neighbors):
"""Find the first candidate that does not overlap with the swarm."""
- pass
+ for xyr_i in candidates:
+ if not any(self.overlap(xyr_i, xyr_j) for xyr_j in neighbors):
+ return xyr_i
+ return candidates[0] # If all overlap, return original position
+
+ def overlap(self, xyr_i, xyr_j):
+ """Check if two points overlap."""
+ xi, yi, ri = xyr_i
+ xj, yj, rj = xyr_j
+ return (xi - xj) ** 2 + (yi - yj) ** 2 < (ri + rj) ** 2
def add_gutters(self, points, center, trans_fwd, trans_inv):
"""Stop points from extending beyond their territory."""
- pass
+ half_width = self.width / 2
+ low_gutter = trans_inv(trans_fwd(center) - half_width)
+ high_gutter = trans_inv(trans_fwd(center) + half_width)
+
+ if self.orient == "y":
+ low_gutter, high_gutter = high_gutter, low_gutter
+
+ np.clip(points, low_gutter, high_gutter, out=points)
BoxPlotArtists = namedtuple('BoxPlotArtists',
@@ -1074,11 +1109,15 @@ class BoxPlotContainer:
def __getitem__(self, idx):
pair_slice = slice(2 * idx, 2 * idx + 2)
- return BoxPlotArtists(self.boxes[idx] if self.boxes else [], self.
- medians[idx] if self.medians else [], self.whiskers[pair_slice] if
- self.whiskers else [], self.caps[pair_slice] if self.caps else
- [], self.fliers[idx] if self.fliers else [], self.means[idx] if
- self.means else [])
+ return BoxPlotArtists(
+ box=self.boxes[idx] if self.boxes else [],
+ median=self.medians[idx] if self.medians else [],
+ whiskers=self.whiskers[pair_slice] if self.whiskers else [],
+ caps=self.caps[pair_slice] if self.caps else [],
+ fliers=self.fliers[idx] if self.fliers else [],
+ mean=self.means[idx] if self.means else []
+ )
def __iter__(self):
- yield from (self[i] for i in range(len(self.boxes)))
+ for i in range(len(self.boxes)):
+ yield self[i]
diff --git a/seaborn/distributions.py b/seaborn/distributions.py
index 36572494..279ecac0 100644
--- a/seaborn/distributions.py
+++ b/seaborn/distributions.py
@@ -73,46 +73,115 @@ class _DistributionPlotter(VectorPlotter):
@property
def univariate(self):
"""Return True if only x or y are used."""
- pass
+ return bool(self.variables.get("x")) != bool(self.variables.get("y"))
@property
def data_variable(self):
"""Return the variable with data for univariate plots."""
- pass
+ return "x" if self.variables.get("x") else "y"
@property
def has_xy_data(self):
"""Return True at least one of x or y is defined."""
- pass
+ return bool(self.variables.get("x")) or bool(self.variables.get("y"))
def _add_legend(self, ax_obj, artist, fill, element, multiple, alpha,
artist_kws, legend_kws):
"""Add artists that reflect semantic mappings and put then in a legend."""
- pass
+ handles = []
+ labels = []
+ for level in self.var_levels:
+ if fill:
+ handle = plt.Rectangle((0, 0), 0, 0, **artist_kws)
+ else:
+ handle = plt.Line2D([], [], **artist_kws)
+ handles.append(handle)
+ labels.append(level)
+
+ legend = ax_obj.legend(handles, labels, **legend_kws)
+ return legend
def _artist_kws(self, kws, fill, element, multiple, color, alpha):
"""Handle differences between artists in filled/unfilled plots."""
- pass
+ if fill:
+ kws.setdefault("facecolor", color)
+ kws.setdefault("edgecolor", "none")
+ else:
+ kws.setdefault("color", color)
+
+ if element == "bars":
+ kws.setdefault("edgecolor", "none")
+ elif element in ["step", "poly"]:
+ kws.setdefault("linewidth", 2)
+
+ if multiple in ["stack", "fill"]:
+ kws["alpha"] = 1 if alpha is None else alpha
+ else:
+ kws.setdefault("alpha", 0.5 if alpha is None else alpha)
+
+ return kws
def _quantile_to_level(self, data, quantile):
"""Return data levels corresponding to quantile cuts of mass."""
- pass
+ isoprop = np.asarray(quantile)
+ values = np.ravel(data)
+ sorted_values = np.sort(values)[::-1]
+ normalized_values = np.cumsum(sorted_values) / values.sum()
+ idx = np.searchsorted(normalized_values, 1 - isoprop)
+ levels = np.take(sorted_values, idx, mode="clip")
+ return levels
def _cmap_from_color(self, color):
"""Return a sequential colormap given a color seed."""
- pass
+ rgb = mpl.colors.to_rgb(color)
+ light_rgb = [1 - (1 - c) * .25 for c in rgb]
+ colors = [light_rgb, rgb]
+ return mpl.colors.LinearSegmentedColormap.from_list("blend", colors)
def _default_discrete(self):
"""Find default values for discrete hist estimation based on variable type."""
- pass
+ if self.univariate:
+ data = self.plot_data[self.data_variable]
+ else:
+ data = self.plot_data[["x", "y"]]
+
+ if pd.api.types.is_integer_dtype(data):
+ discrete = True
+ binwidth = 1
+ else:
+ discrete = False
+ binwidth = None
+
+ return discrete, binwidth
def _resolve_multiple(self, curves, multiple):
"""Modify the density data structure to handle multiple densities."""
- pass
+ if multiple == "layer":
+ return curves
+ elif multiple == "stack":
+ return np.cumsum(curves, axis=0)
+ elif multiple == "fill":
+ cumulative = np.cumsum(curves, axis=0)
+ return cumulative / cumulative.max(axis=0)
+ else:
+ raise ValueError(f"multiple must be 'layer', 'stack', or 'fill', not {multiple}")
def _plot_single_rug(self, sub_data, var, height, ax, kws):
"""Draw a rugplot along one axis of the plot."""
- pass
+ kws = kws.copy()
+ if var == "x":
+ trans = tx.blended_transform_factory(ax.transData, ax.transAxes)
+ xy = np.column_stack([sub_data, np.zeros_like(sub_data)])
+ kws["height"] = height
+ else:
+ trans = tx.blended_transform_factory(ax.transAxes, ax.transData)
+ xy = np.column_stack([np.zeros_like(sub_data), sub_data])
+ kws["width"] = height
+
+ ax.tick_params(direction="in")
+ kws.setdefault("linewidth", 1)
+ lines = LineCollection(np.expand_dims(xy, 1), transform=trans, **kws)
+ ax.add_collection(lines)
histplot.__doc__ = (
@@ -537,7 +606,14 @@ about the breadth of options available for each plot kind.
def _freedman_diaconis_bins(a):
"""Calculate number of hist bins using Freedman-Diaconis rule."""
- pass
+ a = np.asarray(a)
+ if len(a) < 2:
+ return 1
+ h = 2 * (np.percentile(a, 75) - np.percentile(a, 25))
+ if h == 0:
+ return int(np.sqrt(a.size))
+ else:
+ return int(np.ceil((a.max() - a.min()) / h))
def distplot(a=None, bins=None, hist=True, kde=True, rug=False, fit=None,
@@ -556,4 +632,101 @@ def distplot(a=None, bins=None, hist=True, kde=True, rug=False, fit=None,
https://gist.github.com/mwaskom/de44147ed2974457ad6372750bbe5751
"""
- pass
+ import warnings
+ warnings.warn(
+ "distplot is a deprecated function and will be removed in seaborn v0.14.0. "
+ "Please adapt your code to use either displot (a figure-level function with "
+ "similar flexibility) or histplot (an axes-level function for histograms).",
+ FutureWarning
+ )
+
+ if x is not None:
+ a = x
+ warnings.warn(
+ "The `x` parameter has been renamed to `a`. "
+ "Please update your code as the `x` parameter will be removed in v0.14.0.",
+ FutureWarning
+ )
+
+ if ax is None:
+ ax = plt.gca()
+
+ # Intelligently label the support axis
+ label_ax = axlabel or (label if label is not None else None)
+
+ # Make a a 1-d array
+ a = np.asarray(a).squeeze()
+
+ # Decide if the hist is normed
+ norm_hist = norm_hist or kde
+
+ # Handle dictionary defaults
+ hist_kws = {} if hist_kws is None else hist_kws.copy()
+ kde_kws = {} if kde_kws is None else kde_kws.copy()
+ rug_kws = {} if rug_kws is None else rug_kws.copy()
+ fit_kws = {} if fit_kws is None else fit_kws.copy()
+
+ # Get the color from the current color cycle
+ if color is None:
+ color = next(ax._get_lines.prop_cycler)['color']
+
+ # Plug the label into the right kwarg dictionary
+ if label is not None:
+ if hist:
+ hist_kws['label'] = label
+ elif kde:
+ kde_kws['label'] = label
+ elif rug:
+ rug_kws['label'] = label
+ elif fit:
+ fit_kws['label'] = label
+
+ if hist:
+ if bins is None:
+ bins = min(_freedman_diaconis_bins(a), 50)
+ hist_kws.setdefault('alpha', 0.4)
+ hist_kws.setdefault('density', norm_hist)
+
+ orientation = 'horizontal' if vertical else 'vertical'
+ hist_color = hist_kws.pop('color', color)
+ ax.hist(a, bins=bins, orientation=orientation,
+ color=hist_color, **hist_kws)
+
+ if hist_color != color:
+ hist_kws['color'] = hist_color
+
+ if kde:
+ kde_color = kde_kws.pop('color', color)
+ kdeplot(a, vertical=vertical, ax=ax, color=kde_color, **kde_kws)
+ if kde_color != color:
+ kde_kws['color'] = kde_color
+
+ if rug:
+ rug_color = rug_kws.pop('color', color)
+ axis = 'y' if vertical else 'x'
+ rugplot(a, axis=axis, ax=ax, color=rug_color, **rug_kws)
+ if rug_color != color:
+ rug_kws['color'] = rug_color
+
+ if fit is not None:
+ fit_color = fit_kws.pop('color', '#282828')
+ gridsize = fit_kws.pop('gridsize', 200)
+ cut = fit_kws.pop('cut', 3)
+ clip = fit_kws.pop('clip', (-np.inf, np.inf))
+ bw = gaussian_kde(a).scotts_factor() * a.std(ddof=1)
+ x = _kde_support(a, bw, gridsize, cut, clip)
+ params = fit(*x.T).params
+ y = fit.pdf(x, *params)
+ if vertical:
+ x, y = y, x
+ ax.plot(x, y, color=fit_color, **fit_kws)
+ if fit_color != '#282828':
+ fit_kws['color'] = fit_color
+
+ if label_ax is not None:
+ if vertical:
+ ax.set_ylabel(label_ax)
+ else:
+ ax.set_xlabel(label_ax)
+
+ return ax
diff --git a/seaborn/external/appdirs.py b/seaborn/external/appdirs.py
index dc520cd6..b7fba415 100644
--- a/seaborn/external/appdirs.py
+++ b/seaborn/external/appdirs.py
@@ -83,7 +83,28 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
This can be disabled with the `opinion=False` option.
"""
- pass
+ if system == "win32":
+ if appauthor is None:
+ appauthor = appname
+ path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+ if opinion:
+ path = os.path.join(path, "Cache")
+ elif system == 'darwin':
+ path = os.path.expanduser('~/Library/Caches')
+ if appname:
+ path = os.path.join(path, appname)
+ else:
+ path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
+ if appname:
+ path = os.path.join(path, appname)
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
def _get_win_folder_from_registry(csidl_name):
@@ -91,7 +112,20 @@ def _get_win_folder_from_registry(csidl_name):
registry for this guarantees us the correct answer for all CSIDL_*
names.
"""
- pass
+ import winreg as _winreg
+
+ shell_folder_name = {
+ "CSIDL_APPDATA": "AppData",
+ "CSIDL_COMMON_APPDATA": "Common AppData",
+ "CSIDL_LOCAL_APPDATA": "Local AppData",
+ }[csidl_name]
+
+ key = _winreg.OpenKey(
+ _winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
+ )
+ dir, type = _winreg.QueryValueEx(key, shell_folder_name)
+ return dir
if system == 'win32':
diff --git a/seaborn/external/docscrape.py b/seaborn/external/docscrape.py
index c3814e10..17f2ec82 100644
--- a/seaborn/external/docscrape.py
+++ b/seaborn/external/docscrape.py
@@ -39,7 +39,13 @@ import sys
def strip_blank_lines(l):
"""Remove leading and trailing blank lines from a list of lines"""
- pass
+ # Strip leading blank lines
+ while l and not l[0].strip():
+ l.pop(0)
+ # Strip trailing blank lines
+ while l and not l[-1].strip():
+ l.pop()
+ return l
class Reader:
@@ -136,7 +142,31 @@ class NumpyDocString(Mapping):
func_name1, func_name2, :meth:`func_name`, func_name3
"""
- pass
+ result = []
+ current_func = None
+ current_desc = []
+
+ for line in content:
+ line = line.strip()
+ if not line:
+ continue
+
+ match = self._line_rgx.match(line)
+ if match:
+ if current_func:
+ result.append((current_func, ' '.join(current_desc)))
+ current_func = match.group('allfuncs')
+ current_desc = [match.group('desc') or '']
+ else:
+ if current_desc:
+ current_desc.append(line)
+ else:
+ result.append((line, ''))
+
+ if current_func:
+ result.append((current_func, ' '.join(current_desc)))
+
+ return result
def _parse_index(self, section, content):
"""
@@ -144,11 +174,38 @@ class NumpyDocString(Mapping):
:refguide: something, else, and more
"""
- pass
+ result = {}
+ for line in content:
+ line = line.strip()
+ if line.startswith(':'):
+ key, value = line[1:].split(':', 1)
+ key = key.strip()
+ value = value.strip()
+ result[key] = value.split(', ')
+ else:
+ result['default'] = line.strip()
+ return result
def _parse_summary(self):
"""Grab signature (if given) and summary"""
- pass
+ if self._is_at_section():
+ return
+
+ # If several signatures present, take the last one
+ while True:
+ summary = self._doc.read_to_next_empty_line()
+ summary_str = " ".join([s.strip() for s in summary]).strip()
+ if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').search(summary_str):
+ self['Signature'] = summary_str
+ if not self._is_at_section():
+ continue
+ break
+
+ if summary is not None:
+ self['Summary'] = summary
+
+ if not self._is_at_section():
+ self['Extended Summary'] = self._read_to_next_section()
def __str__(self, func_role=''):
out = []
@@ -170,7 +227,14 @@ class NumpyDocString(Mapping):
def dedent_lines(lines):
"""Deindent a list of lines maximally"""
- pass
+ if not lines:
+ return lines
+
+ # Find minimum indentation
+ min_indent = min(len(line) - len(line.lstrip()) for line in lines if line.strip())
+
+ # Dedent lines
+ return [line[min_indent:] if line.strip() else line for line in lines]
class FunctionDoc(NumpyDocString):
diff --git a/seaborn/external/kde.py b/seaborn/external/kde.py
index 4765f446..97dde721 100644
--- a/seaborn/external/kde.py
+++ b/seaborn/external/kde.py
@@ -204,7 +204,37 @@ class gaussian_kde:
the dimensionality of the KDE.
"""
- pass
+ points = atleast_2d(asarray(points))
+
+ d, m = points.shape
+ if d != self.d:
+ if d == 1 and m == self.d:
+ # points was passed in as a row vector
+ points = reshape(points, (self.d, 1))
+ m = 1
+ else:
+ raise ValueError("points have dimension %s, dataset has dimension %s" % (d, self.d))
+
+ result = zeros((m,), dtype=float)
+
+ if m >= self.n:
+ # there are more points than data, so loop over data
+ for i in range(self.n):
+ diff = self.dataset[:, i, newaxis] - points
+ tdiff = dot(self.inv_cov, diff)
+ energy = sum(diff * tdiff, axis=0) / 2.0
+ result = result + self.weights[i] * exp(-energy)
+ else:
+ # loop over points
+ for i in range(m):
+ diff = self.dataset - points[:, i, newaxis]
+ tdiff = dot(self.inv_cov, diff)
+ energy = sum(diff * tdiff, axis=0) / 2.0
+ result[i] = sum(self.weights * exp(-energy))
+
+ result = result / self._norm_factor
+
+ return result
__call__ = evaluate
def scotts_factor(self):
@@ -215,7 +245,7 @@ class gaussian_kde:
s : float
Scott's factor.
"""
- pass
+ return power(self.neff, -1./(self.d+4))
def silverman_factor(self):
"""Compute the Silverman factor.
@@ -225,7 +255,7 @@ class gaussian_kde:
s : float
The silverman factor.
"""
- pass
+ return power(self.neff * (self.d + 2.0) / 4.0, -1. / (self.d + 4))
covariance_factor = scotts_factor
covariance_factor.__doc__ = """Computes the coefficient (`kde.factor`) that
multiplies the data covariance matrix to obtain the kernel covariance
@@ -254,13 +284,40 @@ class gaussian_kde:
.. versionadded:: 0.11
"""
- pass
+ if bw_method is None:
+ pass
+ elif bw_method == 'scott':
+ self.covariance_factor = self.scotts_factor
+ elif bw_method == 'silverman':
+ self.covariance_factor = self.silverman_factor
+ elif np.isscalar(bw_method) and not isinstance(bw_method, str):
+ self._bw_method = 'use constant'
+ self.covariance_factor = lambda: bw_method
+ elif callable(bw_method):
+ self._bw_method = bw_method
+ self.covariance_factor = lambda: self._bw_method(self)
+ else:
+ msg = "`bw_method` should be 'scott', 'silverman', a scalar " \
+ "or a callable."
+ raise ValueError(msg)
+
+ self._compute_covariance()
def _compute_covariance(self):
"""Computes the covariance matrix for each Gaussian kernel using
covariance_factor().
"""
- pass
+ self.factor = self.covariance_factor()
+ # Cache covariance and inverse covariance of the data
+ if not hasattr(self, '_data_inv_cov'):
+ self._data_covariance = atleast_2d(cov(self.dataset, rowvar=1,
+ bias=False,
+ aweights=self.weights))
+ self._data_inv_cov = linalg.inv(self._data_covariance)
+
+ self.covariance = self._data_covariance * self.factor**2
+ self.inv_cov = self._data_inv_cov / self.factor**2
+ self._norm_factor = sqrt(linalg.det(2*pi*self.covariance)) * self.n
def pdf(self, x):
"""
@@ -272,4 +329,4 @@ class gaussian_kde:
docstring for more details.
"""
- pass
+ return self.evaluate(x)
diff --git a/seaborn/external/version.py b/seaborn/external/version.py
index 1dfe1611..3ba73baa 100644
--- a/seaborn/external/version.py
+++ b/seaborn/external/version.py
@@ -220,4 +220,21 @@ def _parse_local_version(local: str) ->Optional[LocalType]:
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
- pass
+ if local is None:
+ return None
+
+ parts = []
+ for part in _local_version_separators.split(local):
+ if not part:
+ continue
+
+ # Try to convert to integer if possible
+ try:
+ parts.append(int(part))
+ except ValueError:
+ parts.append(part)
+
+ if len(parts) == 0:
+ return None
+
+ return tuple(parts)
diff --git a/seaborn/miscplot.py b/seaborn/miscplot.py
index 3bbc63a0..30ea1f0b 100644
--- a/seaborn/miscplot.py
+++ b/seaborn/miscplot.py
@@ -16,9 +16,25 @@ def palplot(pal, size=1):
scaling factor for size of plot
"""
- pass
+ n = len(pal)
+ fig, ax = plt.subplots(1, 1, figsize=(n * size, size))
+ ax.imshow(np.arange(n).reshape(1, n),
+ cmap=mpl.colors.ListedColormap(list(pal)),
+ interpolation="nearest", aspect="auto")
+ ax.set_xticks(np.arange(n) - 0.5)
+ ax.set_yticks([-0.5, 0.5])
+ ax.set_xticklabels([])
+ ax.set_yticklabels([])
+ ax.tick_params(axis='both', which='both', length=0)
+ ax.set_xlim(-0.5, n - 0.5)
+ ax.set_ylim(0.5, -0.5)
def dogplot(*_, **__):
"""Who's a good boy?"""
- pass
+ fig, ax = plt.subplots()
+ ax.text(0.5, 0.5, "Woof!", ha='center', va='center', fontsize=30)
+ ax.set_xlim(0, 1)
+ ax.set_ylim(0, 1)
+ ax.axis('off')
+ return fig
diff --git a/seaborn/palettes.py b/seaborn/palettes.py
index 6447384e..67afa6e9 100644
--- a/seaborn/palettes.py
+++ b/seaborn/palettes.py
@@ -238,277 +238,128 @@ def mpl_palette(name, n_colors=6, as_cmap=False):
def _color_to_rgb(color, input):
"""Add some more flexibility to color choices."""
- pass
+ if isinstance(color, str):
+ if input == "hls":
+ return husl.hls_to_rgb(*color)
+ elif input == "husl":
+ return husl.husl_to_rgb(*color)
+ elif input == "xkcd":
+ return xkcd_rgb[color]
+ else:
+ return mpl.colors.to_rgb(color)
+ elif isinstance(color, (tuple, list)):
+ return color
+ else:
+ raise ValueError("Color must be a string or rgb tuple")
def dark_palette(color, n_colors=6, reverse=False, as_cmap=False, input='rgb'):
- """Make a sequential palette that blends from dark to ``color``.
-
- This kind of palette is good for data that range between relatively
- uninteresting low values and interesting high values.
-
- The ``color`` parameter can be specified in a number of ways, including
- all options for defining a color in matplotlib and several additional
- color spaces that are handled by seaborn. You can also use the database
- of named colors from the XKCD color survey.
-
- If you are using the IPython notebook, you can also choose this palette
- interactively with the :func:`choose_dark_palette` function.
-
- Parameters
- ----------
- color : base color for high values
- hex, rgb-tuple, or html color name
- n_colors : int, optional
- number of colors in the palette
- reverse : bool, optional
- if True, reverse the direction of the blend
- as_cmap : bool, optional
- If True, return a :class:`matplotlib.colors.ListedColormap`.
- input : {'rgb', 'hls', 'husl', xkcd'}
- Color space to interpret the input color. The first three options
- apply to tuple inputs and the latter applies to string inputs.
-
- Returns
- -------
- palette
- list of RGB tuples or :class:`matplotlib.colors.ListedColormap`
-
- See Also
- --------
- light_palette : Create a sequential palette with bright low values.
- diverging_palette : Create a diverging palette with two colors.
-
- Examples
- --------
- .. include:: ../docstrings/dark_palette.rst
-
- """
- pass
-
-
-def light_palette(color, n_colors=6, reverse=False, as_cmap=False, input='rgb'
- ):
- """Make a sequential palette that blends from light to ``color``.
-
- The ``color`` parameter can be specified in a number of ways, including
- all options for defining a color in matplotlib and several additional
- color spaces that are handled by seaborn. You can also use the database
- of named colors from the XKCD color survey.
-
- If you are using a Jupyter notebook, you can also choose this palette
- interactively with the :func:`choose_light_palette` function.
-
- Parameters
- ----------
- color : base color for high values
- hex code, html color name, or tuple in `input` space.
- n_colors : int, optional
- number of colors in the palette
- reverse : bool, optional
- if True, reverse the direction of the blend
- as_cmap : bool, optional
- If True, return a :class:`matplotlib.colors.ListedColormap`.
- input : {'rgb', 'hls', 'husl', xkcd'}
- Color space to interpret the input color. The first three options
- apply to tuple inputs and the latter applies to string inputs.
-
- Returns
- -------
- palette
- list of RGB tuples or :class:`matplotlib.colors.ListedColormap`
+ """Make a sequential palette that blends from dark to ``color``."""
+ rgb = _color_to_rgb(color, input)
+ colors = [(0, 0, 0), rgb]
+ pal = blend_palette(colors, n_colors, as_cmap)
+ return pal[::-1] if reverse else pal
- See Also
- --------
- dark_palette : Create a sequential palette with dark low values.
- diverging_palette : Create a diverging palette with two colors.
- Examples
- --------
- .. include:: ../docstrings/light_palette.rst
-
- """
- pass
+def light_palette(color, n_colors=6, reverse=False, as_cmap=False, input='rgb'):
+ """Make a sequential palette that blends from light to ``color``."""
+ rgb = _color_to_rgb(color, input)
+ colors = [(1, 1, 1), rgb]
+ pal = blend_palette(colors, n_colors, as_cmap)
+ return pal[::-1] if reverse else pal
def diverging_palette(h_neg, h_pos, s=75, l=50, sep=1, n=6, center='light',
as_cmap=False):
- """Make a diverging palette between two HUSL colors.
-
- If you are using the IPython notebook, you can also choose this palette
- interactively with the :func:`choose_diverging_palette` function.
-
- Parameters
- ----------
- h_neg, h_pos : float in [0, 359]
- Anchor hues for negative and positive extents of the map.
- s : float in [0, 100], optional
- Anchor saturation for both extents of the map.
- l : float in [0, 100], optional
- Anchor lightness for both extents of the map.
- sep : int, optional
- Size of the intermediate region.
- n : int, optional
- Number of colors in the palette (if not returning a cmap)
- center : {"light", "dark"}, optional
- Whether the center of the palette is light or dark
- as_cmap : bool, optional
- If True, return a :class:`matplotlib.colors.ListedColormap`.
-
- Returns
- -------
- palette
- list of RGB tuples or :class:`matplotlib.colors.ListedColormap`
-
- See Also
- --------
- dark_palette : Create a sequential palette with dark values.
- light_palette : Create a sequential palette with light values.
-
- Examples
- --------
- .. include: ../docstrings/diverging_palette.rst
-
- """
- pass
+ """Make a diverging palette between two HUSL colors."""
+ s, l = s / 100, l / 100
+ h_neg, h_pos = h_neg % 360, h_pos % 360
+
+ if center == 'light':
+ l_center = 0.95
+ elif center == 'dark':
+ l_center = 0.15
+ else:
+ raise ValueError("center must be 'light' or 'dark'")
+
+ if n % 2:
+ n_half = int((n - 1) / 2)
+ pal_neg = husl_palette(n_half + 1, h=h_neg, s=s, l=l)
+ pal_pos = husl_palette(n_half + 1, h=h_pos, s=s, l=l)
+ neg = pal_neg[:-1][::-1]
+ pos = pal_pos[1:]
+ midpoint = [(l_center, l_center, l_center)]
+ else:
+ n_half = int(n / 2)
+ pal_neg = husl_palette(n_half, h=h_neg, s=s, l=l)
+ pal_pos = husl_palette(n_half, h=h_pos, s=s, l=l)
+ neg = pal_neg[::-1]
+ pos = pal_pos
+ midpoint = []
+
+ pal = neg + midpoint + pos
+
+ if sep > 1:
+ pal = blend_palette(pal, n, as_cmap=as_cmap)
+
+ return pal if not as_cmap else mpl.colors.ListedColormap(pal)
def blend_palette(colors, n_colors=6, as_cmap=False, input='rgb'):
- """Make a palette that blends between a list of colors.
-
- Parameters
- ----------
- colors : sequence of colors in various formats interpreted by `input`
- hex code, html color name, or tuple in `input` space.
- n_colors : int, optional
- Number of colors in the palette.
- as_cmap : bool, optional
- If True, return a :class:`matplotlib.colors.ListedColormap`.
-
- Returns
- -------
- palette
- list of RGB tuples or :class:`matplotlib.colors.ListedColormap`
-
- Examples
- --------
- .. include: ../docstrings/blend_palette.rst
-
- """
- pass
+ """Make a palette that blends between a list of colors."""
+ colors = [_color_to_rgb(color, input) for color in colors]
+ name = "blend"
+ pal = mpl.colors.LinearSegmentedColormap.from_list(name, colors)
+ if as_cmap:
+ return pal
+ else:
+ return pal(np.linspace(0, 1, n_colors))
def xkcd_palette(colors):
- """Make a palette with color names from the xkcd color survey.
-
- See xkcd for the full list of colors: https://xkcd.com/color/rgb/
-
- This is just a simple wrapper around the `seaborn.xkcd_rgb` dictionary.
-
- Parameters
- ----------
- colors : list of strings
- List of keys in the `seaborn.xkcd_rgb` dictionary.
-
- Returns
- -------
- palette
- A list of colors as RGB tuples.
-
- See Also
- --------
- crayon_palette : Make a palette with Crayola crayon colors.
-
- """
- pass
+ """Make a palette with color names from the xkcd color survey."""
+ return [xkcd_rgb[name] for name in colors]
def crayon_palette(colors):
- """Make a palette with color names from Crayola crayons.
-
- Colors are taken from here:
- https://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors
-
- This is just a simple wrapper around the `seaborn.crayons` dictionary.
-
- Parameters
- ----------
- colors : list of strings
- List of keys in the `seaborn.crayons` dictionary.
-
- Returns
- -------
- palette
- A list of colors as RGB tuples.
-
- See Also
- --------
- xkcd_palette : Make a palette with named colors from the XKCD color survey.
-
- """
- pass
+ """Make a palette with color names from Crayola crayons."""
+ return [crayons[name] for name in colors]
def cubehelix_palette(n_colors=6, start=0, rot=0.4, gamma=1.0, hue=0.8,
light=0.85, dark=0.15, reverse=False, as_cmap=False):
- """Make a sequential palette from the cubehelix system.
-
- This produces a colormap with linearly-decreasing (or increasing)
- brightness. That means that information will be preserved if printed to
- black and white or viewed by someone who is colorblind. "cubehelix" is
- also available as a matplotlib-based palette, but this function gives the
- user more control over the look of the palette and has a different set of
- defaults.
-
- In addition to using this function, it is also possible to generate a
- cubehelix palette generally in seaborn using a string starting with
- `ch:` and containing other parameters (e.g. `"ch:s=.25,r=-.5"`).
-
- Parameters
- ----------
- n_colors : int
- Number of colors in the palette.
- start : float, 0 <= start <= 3
- The hue value at the start of the helix.
- rot : float
- Rotations around the hue wheel over the range of the palette.
- gamma : float 0 <= gamma
- Nonlinearity to emphasize dark (gamma < 1) or light (gamma > 1) colors.
- hue : float, 0 <= hue <= 1
- Saturation of the colors.
- dark : float 0 <= dark <= 1
- Intensity of the darkest color in the palette.
- light : float 0 <= light <= 1
- Intensity of the lightest color in the palette.
- reverse : bool
- If True, the palette will go from dark to light.
- as_cmap : bool
- If True, return a :class:`matplotlib.colors.ListedColormap`.
-
- Returns
- -------
- palette
- list of RGB tuples or :class:`matplotlib.colors.ListedColormap`
-
- See Also
- --------
- choose_cubehelix_palette : Launch an interactive widget to select cubehelix
- palette parameters.
- dark_palette : Create a sequential palette with dark low values.
- light_palette : Create a sequential palette with bright low values.
-
- References
- ----------
- Green, D. A. (2011). "A colour scheme for the display of astronomical
- intensity images". Bulletin of the Astromical Society of India, Vol. 39,
- p. 289-295.
-
- Examples
- --------
- .. include:: ../docstrings/cubehelix_palette.rst
-
- """
- pass
+ """Make a sequential palette from the cubehelix system."""
+ def get_color_function(p0, p1):
+ def color(x):
+ # Apply gamma factor to emphasize low or high intensities
+ xg = x ** gamma
+
+ # Calculate amplitude and angle of deviation from the black to white diagonal in the plane of constant
+ # perceived intensity
+ a = hue * xg * (1 - xg) / 2
+
+ phi = 2 * np.pi * (start / 3 + rot * x)
+
+ return xg + a * (p0 * np.cos(phi) + p1 * np.sin(phi))
+ return color
+
+ cdict = {
+ "red": get_color_function(-0.14861, 1.78277),
+ "green": get_color_function(-0.29227, -0.90649),
+ "blue": get_color_function(1.97294, 0.0)
+ }
+
+ cmap = mpl.colors.LinearSegmentedColormap("cubehelix", cdict)
+
+ x = np.linspace(light, dark, n_colors)
+ pal = cmap(x)[:, :3].tolist()
+ if reverse:
+ pal = pal[::-1]
+
+ if as_cmap:
+ return mpl.colors.ListedColormap(pal)
+ else:
+ return pal
def _parse_cubehelix_args(argstr):
diff --git a/seaborn/rcmod.py b/seaborn/rcmod.py
index 28795aea..6a95fc38 100644
--- a/seaborn/rcmod.py
+++ b/seaborn/rcmod.py
@@ -59,7 +59,21 @@ def set_theme(context='notebook', style='darkgrid', palette='deep', font=
.. include:: ../docstrings/set_theme.rst
"""
- pass
+ # Set the context (scaling)
+ set_context(context, font_scale)
+
+ # Set the style
+ set_style(style)
+
+ # Set the color palette
+ set_palette(palette, color_codes=color_codes)
+
+ # Set the font
+ mpl.rcParams['font.family'] = font
+
+ # Override with custom rc parameters if provided
+ if rc is not None:
+ mpl.rcParams.update(rc)
def set(*args, **kwargs):
@@ -68,17 +82,17 @@ def set(*args, **kwargs):
This function may be removed in the future.
"""
- pass
+ return set_theme(*args, **kwargs)
def reset_defaults():
"""Restore all RC params to default settings."""
- pass
+ mpl.rcParams.update(mpl.rcParamsDefault)
def reset_orig():
"""Restore all RC params to original settings (respects custom rc)."""
- pass
+ mpl.rcParams.update(mpl.rcParamsOrig)
def axes_style(style=None, rc=None):
@@ -111,7 +125,40 @@ def axes_style(style=None, rc=None):
.. include:: ../docstrings/axes_style.rst
"""
- pass
+ if style is None:
+ style = {}
+
+ # Define preset styles
+ preset_styles = {
+ 'darkgrid': {'axes.facecolor': '.15', 'axes.edgecolor': 'white',
+ 'axes.grid': True, 'grid.color': 'white'},
+ 'whitegrid': {'axes.facecolor': 'white', 'axes.edgecolor': '.15',
+ 'axes.grid': True, 'grid.color': '.8'},
+ 'dark': {'axes.facecolor': '.15', 'axes.edgecolor': 'white',
+ 'axes.grid': False},
+ 'white': {'axes.facecolor': 'white', 'axes.edgecolor': '.15',
+ 'axes.grid': False},
+ 'ticks': {'axes.facecolor': 'white', 'axes.edgecolor': '.15',
+ 'axes.grid': False, 'xtick.direction': 'out',
+ 'ytick.direction': 'out'}
+ }
+
+ # Use the specified style, or merge with custom rc if provided
+ if isinstance(style, str):
+ if style not in preset_styles:
+ raise ValueError(f"style must be one of {', '.join(preset_styles.keys())}")
+ style_dict = preset_styles[style].copy()
+ elif isinstance(style, dict):
+ style_dict = style.copy()
+ else:
+ style_dict = {}
+
+ # Update with custom rc if provided
+ if rc is not None:
+ style_dict.update(rc)
+
+ # Create and return the _AxesStyle object
+ return _AxesStyle(style_dict)
def set_style(style=None, rc=None):
@@ -142,7 +189,8 @@ def set_style(style=None, rc=None):
.. include:: ../docstrings/set_style.rst
"""
- pass
+ style_dict = axes_style(style, rc).copy()
+ mpl.rcParams.update(style_dict)
def plotting_context(context=None, font_scale=1, rc=None):
@@ -179,7 +227,43 @@ def plotting_context(context=None, font_scale=1, rc=None):
.. include:: ../docstrings/plotting_context.rst
"""
- pass
+ if context is None:
+ context = {}
+
+ # Define preset contexts
+ preset_contexts = {
+ 'paper': {'font.size': 8, 'axes.labelsize': 8, 'axes.titlesize': 9,
+ 'xtick.labelsize': 7, 'ytick.labelsize': 7, 'legend.fontsize': 7},
+ 'notebook': {'font.size': 10, 'axes.labelsize': 10, 'axes.titlesize': 11,
+ 'xtick.labelsize': 9, 'ytick.labelsize': 9, 'legend.fontsize': 9},
+ 'talk': {'font.size': 14, 'axes.labelsize': 14, 'axes.titlesize': 15,
+ 'xtick.labelsize': 12, 'ytick.labelsize': 12, 'legend.fontsize': 12},
+ 'poster': {'font.size': 18, 'axes.labelsize': 18, 'axes.titlesize': 19,
+ 'xtick.labelsize': 16, 'ytick.labelsize': 16, 'legend.fontsize': 16}
+ }
+
+ # Use the specified context, or merge with custom rc if provided
+ if isinstance(context, str):
+ if context not in preset_contexts:
+ raise ValueError(f"context must be one of {', '.join(preset_contexts.keys())}")
+ context_dict = preset_contexts[context].copy()
+ elif isinstance(context, dict):
+ context_dict = context.copy()
+ else:
+ context_dict = preset_contexts['notebook'].copy()
+
+ # Scale font sizes
+ font_keys = ['font.size', 'axes.labelsize', 'axes.titlesize',
+ 'xtick.labelsize', 'ytick.labelsize', 'legend.fontsize']
+ for key in font_keys:
+ context_dict[key] = context_dict[key] * font_scale
+
+ # Update with custom rc if provided
+ if rc is not None:
+ context_dict.update(rc)
+
+ # Create and return the _PlottingContext object
+ return _PlottingContext(context_dict)
def set_context(context=None, font_scale=1, rc=None):
@@ -215,7 +299,8 @@ def set_context(context=None, font_scale=1, rc=None):
.. include:: ../docstrings/set_context.rst
"""
- pass
+ context_dict = plotting_context(context, font_scale, rc).copy()
+ mpl.rcParams.update(context_dict)
class _RCAesthetics(dict):
@@ -274,4 +359,7 @@ def set_palette(palette, n_colors=None, desat=None, color_codes=False):
set_style : set the default parameters for figure style
"""
- pass
+ colors = palettes.color_palette(palette, n_colors, desat)
+ if palettes.SEABORN_PALETTES.get(palette) is not None and color_codes:
+ palettes.set_color_codes(palette)
+ mpl.rcParams["axes.prop_cycle"] = cycler(color=colors)
diff --git a/seaborn/regression.py b/seaborn/regression.py
index 9a41fe79..0a9476f6 100644
--- a/seaborn/regression.py
+++ b/seaborn/regression.py
@@ -28,11 +28,22 @@ class _LinearPlotter:
def establish_variables(self, data, **kws):
"""Extract variables from data or use directly."""
- pass
+ self.data = data
+ for key, val in kws.items():
+ if isinstance(val, str):
+ kws[key] = data[val]
+ elif isinstance(val, list) and all(isinstance(v, str) for v in val):
+ kws[key] = data[val].values.T
+ self.__dict__.update(kws)
def dropna(self, *vars):
"""Remove observations with missing data."""
- pass
+ if vars:
+ obs = pd.notnull(self.data[list(vars)]).all(axis=1)
+ if hasattr(self, "weights"):
+ self.weights = self.weights[obs]
+ for var in vars:
+ self.__dict__[var] = self.__dict__[var][obs]
class _RegressionPlotter(_LinearPlotter):
@@ -88,60 +99,154 @@ class _RegressionPlotter(_LinearPlotter):
@property
def scatter_data(self):
"""Data where each observation is a point."""
- pass
+ x = self.x
+ y = self.y
+ if self.x_jitter is not None:
+ x = x + np.random.uniform(-self.x_jitter, self.x_jitter, len(x))
+ if self.y_jitter is not None:
+ y = y + np.random.uniform(-self.y_jitter, self.y_jitter, len(y))
+ return x, y
@property
def estimate_data(self):
"""Data with a point estimate and CI for each discrete x value."""
- pass
+ x = self.x_discrete
+ if self.x_estimator is None:
+ return x, None, None
+
+ y = self.y
+ vals, bins = pd.cut(x, bins=self.x_bins, retbins=True)
+ points = bins[:-1] + np.diff(bins) / 2
+
+ if self.x_ci == 'sd':
+ est = y.groupby(vals).agg(['mean', 'std'])
+ ci_low = est['mean'] - est['std']
+ ci_high = est['mean'] + est['std']
+ else:
+ est = y.groupby(vals).agg(self.x_estimator)
+ if self.x_ci is None:
+ ci_low = ci_high = None
+ else:
+ boots = algo.bootstrap(y, vals, func=self.x_estimator,
+ n_boot=self.n_boot, units=self.units)
+ ci_low, ci_high = utils.ci(boots, self.x_ci)
+ return points, est, (ci_low, ci_high)
def _check_statsmodels(self):
"""Check whether statsmodels is installed if any boolean options require it."""
- pass
+ if not _has_statsmodels and (self.robust or self.logistic or self.lowess):
+ raise ImportError("The statsmodels package is required.")
def fit_regression(self, ax=None, x_range=None, grid=None):
"""Fit the regression model."""
+ # Implementation depends on the specific regression type
pass
def fit_fast(self, grid):
"""Low-level regression and prediction using linear algebra."""
- pass
+ y = self.y
+ X = np.c_[np.ones(len(self.x)), self.x]
+ flat = grid.reshape(-1, 1)
+ grid_X = np.c_[np.ones(len(flat)), flat]
+
+ def reg_func(_x, _y):
+ return np.linalg.pinv(_x).dot(_y)
+
+ yhat = grid_X.dot(reg_func(X, y))
+ if self.ci is None:
+ return yhat
+
+ beta_boots = algo.bootstrap(X, y, func=reg_func,
+ n_boot=self.n_boot, units=self.units).T
+ yhat_boots = grid_X.dot(beta_boots).T
+ return yhat, utils.ci(yhat_boots, self.ci)
def fit_poly(self, grid, order):
"""Regression using numpy polyfit for higher-order trends."""
- pass
+ x = self.x
+ y = self.y
+
+ def reg_func(_x, _y):
+ return np.polyval(np.polyfit(_x, _y, order), grid)
+
+ yhat = reg_func(x, y)
+ if self.ci is None:
+ return yhat
+
+ yhat_boots = algo.bootstrap(x, y, func=reg_func,
+ n_boot=self.n_boot, units=self.units)
+ return yhat, utils.ci(yhat_boots, self.ci)
def fit_statsmodels(self, grid, model, **kwargs):
"""More general regression function using statsmodels objects."""
+ # Implementation depends on the specific statsmodels model
pass
def fit_lowess(self):
"""Fit a locally-weighted regression, which returns its own grid."""
- pass
+ from statsmodels.nonparametric.smoothers_lowess import lowess
+ x = self.x
+ y = self.y
+ return lowess(y, x, frac=1. / 3)
def fit_logx(self, grid):
"""Fit the model in log-space."""
- pass
+ x = self.x
+ y = self.y
+
+ def reg_func(_x, _y):
+ return np.polyval(np.polyfit(np.log(_x), _y, 1), np.log(grid))
+
+ yhat = reg_func(x, y)
+ if self.ci is None:
+ return yhat
+
+ yhat_boots = algo.bootstrap(x, y, func=reg_func,
+ n_boot=self.n_boot, units=self.units)
+ return yhat, utils.ci(yhat_boots, self.ci)
def bin_predictor(self, bins):
"""Discretize a predictor by assigning value to closest bin."""
- pass
+ x = self.x
+ if np.isscalar(bins):
+ bins = np.linspace(x.min(), x.max(), bins)
+ else:
+ bins = np.asarray(bins)
+ return bins[np.digitize(x, bins[1:-1])]
def regress_out(self, a, b):
"""Regress b from a keeping a's original mean."""
- pass
+ a_mean = a.mean()
+ a_norm = a - a_mean
+ b_norm = b - b.mean()
+ beta = np.dot(b_norm, a_norm) / np.dot(a_norm, a_norm)
+ return (a_norm - beta * b_norm) + a_mean
def plot(self, ax, scatter_kws, line_kws):
"""Draw the full plot."""
- pass
+ if ax is None:
+ ax = plt.gca()
+
+ # Plot the scatter points
+ if self.scatter:
+ self.scatterplot(ax, scatter_kws)
+
+ # Plot the regression line
+ if self.fit_reg:
+ self.lineplot(ax, line_kws)
+
+ return ax
def scatterplot(self, ax, kws):
"""Draw the data."""
- pass
+ x, y = self.scatter_data
+ ax.scatter(x, y, **kws)
def lineplot(self, ax, kws):
"""Draw the model."""
- pass
+ x = np.linspace(self.x.min(), self.x.max(), 100)
+ y = self.fit_regression(grid=x)
+ ax.plot(x, y, **kws)
_regression_docs = dict(model_api=dedent(
@@ -477,4 +582,41 @@ def residplot(data=None, *, x=None, y=None, x_partial=None, y_partial=None,
.. include:: ../docstrings/residplot.rst
"""
- pass
+ plotter = _RegressionPlotter(x, y, data, x_partial, y_partial,
+ order=order, robust=robust, dropna=dropna)
+
+ if ax is None:
+ ax = plt.gca()
+
+ # Fit the regression model
+ grid = np.linspace(plotter.x.min(), plotter.x.max(), 100)
+ yhat = plotter.fit_regression(grid=grid)
+
+ # Calculate residuals
+ resid = plotter.y - np.interp(plotter.x, grid, yhat)
+
+ # Set up the scatter plot parameters
+ if scatter_kws is None:
+ scatter_kws = {}
+ if color is not None:
+ scatter_kws.setdefault("color", color)
+
+ # Plot the residuals
+ ax.scatter(plotter.x, resid, **scatter_kws)
+
+ # Add the lowess line if requested
+ if lowess:
+ from statsmodels.nonparametric.smoothers_lowess import lowess as sm_lowess
+ smooth = sm_lowess(resid, plotter.x)
+ if line_kws is None:
+ line_kws = {}
+ if color is not None:
+ line_kws.setdefault("color", color)
+ ax.plot(smooth[:, 0], smooth[:, 1], **line_kws)
+
+ # Adjust the plot
+ ax.axhline(0, color=".2", linestyle="--")
+ ax.set_xlabel(plotter.x_var)
+ ax.set_ylabel("Residuals")
+
+ return ax
diff --git a/seaborn/relational.py b/seaborn/relational.py
index 76efb2d1..63bc9243 100644
--- a/seaborn/relational.py
+++ b/seaborn/relational.py
@@ -206,7 +206,43 @@ class _LinePlotter(_RelationalPlotter):
def plot(self, ax, kws):
"""Draw the plot onto an axes, passing matplotlib kwargs."""
- pass
+ # Set up the data
+ self.parse_data(self.data, self.variables)
+ data = self.plot_data
+
+ # Handle sorting
+ if self.sort:
+ data = data.sort_values(self.orient)
+
+ # Draw the main line
+ line_kws = dict(color=self.colors[0], linewidth=self.sizes[0])
+ line_kws.update(kws)
+ line, = ax.plot(data[self.orient], data[self.value_label], **line_kws)
+
+ # Add error bars if needed
+ if self.errorbar is not None:
+ err_kws = self.err_kws.copy()
+ err_kws.setdefault("color", line.get_color())
+ err_style = self.err_style or ("band" if self.orient == "x" else "bars")
+
+ if err_style == "band":
+ ax.fill_between(data[self.orient],
+ data[self.value_label] - data[self.errorbar],
+ data[self.value_label] + data[self.errorbar],
+ alpha=0.2, **err_kws)
+ elif err_style == "bars":
+ ax.errorbar(data[self.orient], data[self.value_label],
+ yerr=data[self.errorbar], fmt="none", **err_kws)
+
+ # Set the axis labels
+ if self.orient == "x":
+ ax.set_xlabel(self.x_label)
+ ax.set_ylabel(self.y_label)
+ else:
+ ax.set_xlabel(self.y_label)
+ ax.set_ylabel(self.x_label)
+
+ return ax
class _ScatterPlotter(_RelationalPlotter):
@@ -218,6 +254,49 @@ class _ScatterPlotter(_RelationalPlotter):
super().__init__(data=data, variables=variables)
self.legend = legend
+ def plot(self, ax, kws):
+ """Draw the plot onto an axes, passing matplotlib kwargs."""
+ # Set up the data
+ self.parse_data(self.data, self.variables)
+ data = self.plot_data
+
+ # Set up the scatter plot parameters
+ scatter_kws = dict(
+ c=self.colors,
+ s=self.sizes,
+ marker=self.markers[0],
+ alpha=kws.pop("alpha", 0.8),
+ )
+ scatter_kws.update(kws)
+
+ # Draw the scatter plot
+ points = ax.scatter(data[self.x_label], data[self.y_label], **scatter_kws)
+
+ # Set the axis labels
+ ax.set_xlabel(self.x_label)
+ ax.set_ylabel(self.y_label)
+
+ # Handle the legend
+ if self.legend:
+ self._add_legend(ax, points)
+
+ return ax
+
+ def _add_legend(self, ax, points):
+ """Add a legend to the plot."""
+ handles, labels = [], []
+ for level in self.hue_levels:
+ handle = _scatter_legend_artist(
+ points,
+ c=self.colors[level],
+ s=self.sizes[level],
+ marker=self.markers[level],
+ )
+ handles.append(handle)
+ labels.append(level)
+
+ ax.legend(handles, labels, title=self.hue_label)
+
lineplot.__doc__ = (
"""Draw a line plot with possibility of several semantic groupings.
diff --git a/seaborn/utils.py b/seaborn/utils.py
index ba736ec6..6e1c3b2b 100644
--- a/seaborn/utils.py
+++ b/seaborn/utils.py
@@ -39,17 +39,34 @@ def ci_to_errsize(cis, heights):
format as argument for plt.bar
"""
- pass
+ cis = np.asarray(cis)
+ heights = np.asarray(heights)
+ errsize = []
+ for i in range(len(heights)):
+ low, high = cis[:, i]
+ errsize.append([heights[i] - low, high - heights[i]])
+ return np.array(errsize).T
def _draw_figure(fig):
"""Force draw of a matplotlib figure, accounting for back-compat."""
- pass
+ fig.canvas.draw()
+ if hasattr(fig.canvas, "flush_events"):
+ fig.canvas.flush_events()
def _default_color(method, hue, color, kws, saturation=1):
"""If needed, get a default color by using the matplotlib property cycle."""
- pass
+ if color is not None:
+ return color
+ elif hue is not None:
+ return None
+ else:
+ cycler = mpl.rcParams['axes.prop_cycle']
+ color = next(cycler)['color']
+ if saturation < 1:
+ color = desaturate(color, saturation)
+ return color
def desaturate(color, prop):
@@ -68,7 +85,10 @@ def desaturate(color, prop):
desaturated color code in RGB tuple representation
"""
- pass
+ rgb = mpl.colors.colorConverter.to_rgb(color)
+ hsv = colorsys.rgb_to_hsv(*rgb)
+ hsv = (hsv[0], hsv[1] * prop, hsv[2])
+ return colorsys.hsv_to_rgb(*hsv)
def saturate(color):
@@ -85,7 +105,10 @@ def saturate(color):
saturated color code in RGB tuple representation
"""
- pass
+ rgb = mpl.colors.colorConverter.to_rgb(color)
+ hsv = colorsys.rgb_to_hsv(*rgb)
+ hsv = (hsv[0], 1.0, hsv[2])
+ return colorsys.hsv_to_rgb(*hsv)
def set_hls_values(color, h=None, l=None, s=None):
diff --git a/seaborn/widgets.py b/seaborn/widgets.py
index 3941f65b..14552573 100644
--- a/seaborn/widgets.py
+++ b/seaborn/widgets.py
@@ -12,17 +12,23 @@ __all__ = ['choose_colorbrewer_palette', 'choose_cubehelix_palette',
def _init_mutable_colormap():
"""Create a matplotlib colormap that will be updated by the widgets."""
- pass
+ return LinearSegmentedColormap.from_list("interactive", ["#FFFFFF", "#000000"], N=256)
def _update_lut(cmap, colors):
"""Change the LUT values in a matplotlib colormap in-place."""
- pass
+ cmap._lut[:len(colors)] = colors
+ cmap._lut[len(colors):] = 0
+ cmap._init()
def _show_cmap(cmap):
"""Show a continuous matplotlib colormap."""
- pass
+ fig, ax = plt.subplots(figsize=(12, 0.5))
+ gradient = np.linspace(0, 1, 256).reshape(1, -1)
+ ax.imshow(gradient, aspect='auto', cmap=cmap)
+ ax.set_axis_off()
+ plt.show()
def choose_colorbrewer_palette(data_type, as_cmap=False):
@@ -57,7 +63,24 @@ def choose_colorbrewer_palette(data_type, as_cmap=False):
"""
- pass
+ data_type = data_type.lower()
+ if data_type.startswith("q"):
+ palettes = ["Set1", "Set2", "Set3", "Paired", "Accent", "Pastel1", "Pastel2", "Dark2"]
+ elif data_type.startswith("s"):
+ palettes = ["Blues", "Greens", "Reds", "Oranges", "Purples", "Greys"]
+ elif data_type.startswith("d"):
+ palettes = ["RdBu", "RdGy", "PRGn", "PiYG", "BrBG", "RdYlBu", "RdYlGn"]
+ else:
+ raise ValueError("data_type must be either 'sequential', 'diverging' or 'qualitative'")
+
+ def show_palette(palette_name):
+ pal = color_palette(palette_name)
+ if as_cmap:
+ pal = LinearSegmentedColormap.from_list(palette_name, pal)
+ palplot(pal)
+ return pal
+
+ return interact(show_palette, palette_name=palettes)
def choose_dark_palette(input='husl', as_cmap=False):
@@ -91,7 +114,19 @@ def choose_dark_palette(input='husl', as_cmap=False):
cubehelix system.
"""
- pass
+ def show_dark_palette(color, n_colors, reverse):
+ pal = dark_palette(color, n_colors, reverse=reverse, input=input)
+ if as_cmap:
+ pal = LinearSegmentedColormap.from_list("dark", pal)
+ palplot(pal)
+ return pal
+
+ return interact(
+ show_dark_palette,
+ color=input,
+ n_colors=IntSlider(min=2, max=16, value=8),
+ reverse=False
+ )
def choose_light_palette(input='husl', as_cmap=False):
@@ -125,7 +160,19 @@ def choose_light_palette(input='husl', as_cmap=False):
cubehelix system.
"""
- pass
+ def show_light_palette(color, n_colors, reverse):
+ pal = light_palette(color, n_colors, reverse=reverse, input=input)
+ if as_cmap:
+ pal = LinearSegmentedColormap.from_list("light", pal)
+ palplot(pal)
+ return pal
+
+ return interact(
+ show_light_palette,
+ color=input,
+ n_colors=IntSlider(min=2, max=16, value=8),
+ reverse=False
+ )
def choose_diverging_palette(as_cmap=False):
@@ -156,7 +203,23 @@ def choose_diverging_palette(as_cmap=False):
colorbrewer set, including diverging palettes.
"""
- pass
+ def show_diverging_palette(h_neg, h_pos, s, l, sep, n, center):
+ pal = diverging_palette(h_neg, h_pos, s=s, l=l, sep=sep, n=n, center=center)
+ if as_cmap:
+ pal = LinearSegmentedColormap.from_list("diverging", pal)
+ palplot(pal)
+ return pal
+
+ return interact(
+ show_diverging_palette,
+ h_neg=IntSlider(min=0, max=359, value=220),
+ h_pos=IntSlider(min=0, max=359, value=10),
+ s=IntSlider(min=0, max=99, value=75),
+ l=IntSlider(min=0, max=99, value=50),
+ sep=IntSlider(min=1, max=50, value=10),
+ n=IntSlider(min=2, max=16, value=8),
+ center=["light", "dark"]
+ )
def choose_cubehelix_palette(as_cmap=False):
@@ -187,4 +250,21 @@ def choose_cubehelix_palette(as_cmap=False):
cubehelix system.
"""
- pass
+ def show_cubehelix_palette(n_colors, start, rot, gamma, hue, dark, light, reverse):
+ pal = cubehelix_palette(n_colors, start, rot, gamma, hue, dark, light, reverse)
+ if as_cmap:
+ pal = LinearSegmentedColormap.from_list("cubehelix", pal)
+ palplot(pal)
+ return pal
+
+ return interact(
+ show_cubehelix_palette,
+ n_colors=IntSlider(min=2, max=16, value=8),
+ start=FloatSlider(min=0, max=3, value=0, step=0.1),
+ rot=FloatSlider(min=-1, max=1, value=0.4, step=0.05),
+ gamma=FloatSlider(min=0, max=5, value=1, step=0.05),
+ hue=FloatSlider(min=0, max=1, value=0.8, step=0.05),
+ dark=FloatSlider(min=0, max=1, value=0.15, step=0.05),
+ light=FloatSlider(min=0, max=1, value=0.85, step=0.05),
+ reverse=False
+ )