Repository: altair-viz/altair (python)

Issue:
Internal: overzealous dataset conversion
Example:
```
>>> alt.LookupData.from_dict({'data': {'values': []}, 'key': 'x'}).to_dict()
{'data': {'name': 'data-d751713988987e9331980363e24189ce'}, 'key': 'x'}
```
The issue is it assumes that ``context`` is always relevant; we should properly handle ``context=None``.

Pull request description:
BUG: fix LookupData.to_dict bug (Fixes #1836)

Patch:
diff --git a/altair/_magics.py b/altair/_magics.py
index 77f671ab..3758f767 100644
--- a/altair/_magics.py
+++ b/altair/_magics.py
@@ -118,7 +118,7 @@ def vega(line, cell):
             raise ValueError("%%vega: spec is not valid JSON. "
                              "Install pyyaml to parse spec as yaml")
     else:
-        spec = yaml.load(cell, Loader=yaml.FullLoader)
+        spec = yaml.load(cell)
 
     if data:
         spec['data'] = []
@@ -163,7 +163,7 @@ def vegalite(line, cell):
             raise ValueError("%%vegalite: spec is not valid JSON. "
                              "Install pyyaml to parse spec as yaml")
     else:
-        spec = yaml.load(cell, Loader=yaml.FullLoader)
+        spec = yaml.load(cell)
 
     if args.data is not None:
         data = _get_variable(args.data)
diff --git a/altair/examples/density_facet.py b/altair/examples/density_facet.py
deleted file mode 100644
index e8b1bbe7..00000000
--- a/altair/examples/density_facet.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""
-Faceted Density Estimates
--------------------------
-Density estimates of measurements for each iris flower feature
-"""
-# category: area charts
-
-import altair as alt
-from vega_datasets import data
-
-source = data.iris()
-
-alt.Chart(source).transform_fold(
-    ['petalWidth', 
-     'petalLength', 
-     'sepalWidth', 
-     'sepalLength'], 
-    as_ = ['Measurement_type', 'value']
-).transform_density(
-    density='value', 
-    bandwidth=0.3, 
-    groupby=['Measurement_type'], 
-    extent= [0, 8]
-).mark_area().encode(
-    alt.X('value:Q'), 
-    alt.Y('density:Q'),
-    alt.Row('Measurement_type:N')
-).properties(width=300, height=50)
diff --git a/altair/examples/density_stack.py b/altair/examples/density_stack.py
deleted file mode 100644
index 2fdbdc06..00000000
--- a/altair/examples/density_stack.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""
-Stacked Density Estimates
--------------------------
-To plot a stacked graph of estimates, use a shared ``extent`` and a fixed
-number of subdivision ``steps`` to ensure that the points for each area align
-well.  Density estimates of measurements for each iris flower feature are plot
-in a stacked method.  In addition, setting ``counts`` to true multiplies the
-densities by the number of data points in each group, preserving proportional
-differences.
-"""
-# category: area charts
-
-import altair as alt
-from vega_datasets import data
-
-source = data.iris()
-
-alt.Chart(source).transform_fold(
-    ['petalWidth', 
-     'petalLength', 
-     'sepalWidth', 
-     'sepalLength'], 
-    as_ = ['Measurement_type', 'value']
-).transform_density(
-    density='value', 
-    bandwidth=0.3, 
-    groupby=['Measurement_type'], 
-    extent= [0, 8], 
-    counts = True, 
-    steps=200
-).mark_area().encode(
-    alt.X('value:Q'), 
-    alt.Y('density:Q', stack='zero'),
-    alt.Color('Measurement_type:N')
-).properties(width=400, height=100)
diff --git a/altair/examples/poly_fit.py b/altair/examples/poly_fit.py
new file mode 100644
index 00000000..0ff9c647
--- /dev/null
+++ b/altair/examples/poly_fit.py
@@ -0,0 +1,41 @@
+"""
+Polynomial Fit Plot
+===================
+This example shows how to overlay data with a fitted polynomial
+"""
+# category: scatter plots
+import numpy as np
+import pandas as pd
+import altair as alt
+
+# Generate some random data
+rng = np.random.RandomState(1)
+x = rng.rand(40) ** 2
+y = 10 - 1. / (x + 0.1) + rng.randn(40)
+df = pd.DataFrame({'x': x, 'y': y})
+
+# Define the degree of the polynomial fit
+degree_list = [1, 3, 5]
+
+# Build a dataframe with the fitted data
+poly_data = pd.DataFrame({'xfit': np.linspace(df['x'].min(), df['x'].max(), 500)})
+for degree in degree_list:
+    poly_data[str(degree)] = np.poly1d(np.polyfit(df['x'], df['y'], degree))(poly_data['xfit'])
+
+# Plot the data points on an interactive axis
+points = alt.Chart(df).mark_circle(color='black').encode(
+    x=alt.X('x', title='x'),
+    y=alt.Y('y', title='y')
+).interactive()
+
+# Plot the best fit polynomials
+polynomial_fit = alt.Chart(poly_data).transform_fold(
+    ['1', '3', '5'],
+    as_=['degree', 'yfit']
+).mark_line().encode(
+    x='xfit:Q',
+    y='yfit:Q',
+    color='degree:N'
+)
+
+points + polynomial_fit
diff --git a/altair/examples/poly_fit_regression.py b/altair/examples/poly_fit_regression.py
deleted file mode 100644
index 0e710532..00000000
--- a/altair/examples/poly_fit_regression.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
-Polynomial Fit Plot with Regression Transform
-=============================================
-This example shows how to overlay data with multiple fitted polynomials using
-the regression transform.
-"""
-# category: scatter plots
-
-import numpy as np
-import pandas as pd
-import altair as alt
-
-# Generate some random data
-rng = np.random.RandomState(1)
-x = rng.rand(40) ** 2
-y = 10 - 1.0 / (x + 0.1) + rng.randn(40)
-source = pd.DataFrame({"x": x, "y": y})
-
-# Define the degree of the polynomial fits
-degree_list = [1, 3, 5]
-
-base = alt.Chart(source).mark_circle(color="black").encode(
-        alt.X("x"), alt.Y("y")
-)
-
-polynomial_fit = [
-    base.transform_regression(
-        "x", "y", method="poly", order=order, as_=["x", str(order)]
-    )
-    .mark_line()
-    .transform_fold([str(order)], as_=["degree", "y"])
-    .encode(alt.Color("degree:N"))
-    for order in degree_list
-]
-
-alt.layer(base, *polynomial_fit)
diff --git a/altair/examples/scatter_image_marks.py b/altair/examples/scatter_image_marks.py
deleted file mode 100644
index b84073e2..00000000
--- a/altair/examples/scatter_image_marks.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-Scatter Plot with Image Marks
-----------------------------
-This example shows how to make a scatter plot using images as markers. 
-"""
-# category: scatter plots
-
-import altair as alt
-import pandas as pd
-
-source = pd.DataFrame.from_records([
-      {"x": 0.5, "y": 0.5, "img": "https://vega.github.io/vega-datasets/data/ffox.png"},
-      {"x": 1.5, "y": 1.5, "img": "https://vega.github.io/vega-datasets/data/gimp.png"},
-      {"x": 2.5, "y": 2.5, "img": "https://vega.github.io/vega-datasets/data/7zip.png"}
-])
-
-alt.Chart(source).mark_image(
-    width=50, 
-    height=50
-).encode(
-    x='x', 
-    y='y', 
-    url='img'
-)
diff --git a/altair/examples/scatter_qq.py b/altair/examples/scatter_qq.py
deleted file mode 100644
index b8abfe72..00000000
--- a/altair/examples/scatter_qq.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-Quantile-Quantile Plot
-----------------------
-A quantile-quantile plot comparing input data to theoretical distributions.
-"""
-# category: scatter plots
-
-import altair as alt
-from vega_datasets import data
-
-source = data.normal_2d.url
-
-base = alt.Chart(source).transform_quantile(
-    'u',
-    step=0.01,
-    as_ = ['p', 'v']
-).transform_calculate(
-    uniform = 'quantileUniform(datum.p)',
-    normal = 'quantileNormal(datum.p)'
-).mark_point().encode(
-    alt.Y('v:Q')
-)
-
-base.encode(x='uniform:Q') | base.encode(x='normal:Q')
diff --git a/altair/examples/wilkinson-dot-plot.py b/altair/examples/wilkinson-dot-plot.py
index 05a5786d..2daee281 100644
--- a/altair/examples/wilkinson-dot-plot.py
+++ b/altair/examples/wilkinson-dot-plot.py
@@ -1,7 +1,7 @@
 """
 Wilkinson Dot Plot
 ------------------
-An example of a `Wilkinson Dot Plot <https://en.wikipedia.org/wiki/Dot_plot_(statistics)>`_
+An example of a [Wilkinson Dot Plot](https://en.wikipedia.org/wiki/Dot_plot_(statistics))
 """
 # category: other charts
 
diff --git a/altair/utils/core.py b/altair/utils/core.py
index 73b1cfea..a9edacb8 100644
--- a/altair/utils/core.py
+++ b/altair/utils/core.py
@@ -1,7 +1,7 @@
 """
 Utility routines
 """
-from collections.abc import Mapping
+import collections
 from copy import deepcopy
 import json
 import itertools
@@ -454,9 +454,9 @@ def update_nested(original, update, copy=False):
     if copy:
         original = deepcopy(original)
     for key, val in update.items():
-        if isinstance(val, Mapping):
+        if isinstance(val, collections.Mapping):
             orig_val = original.get(key, {})
-            if isinstance(orig_val, Mapping):
+            if isinstance(orig_val, collections.Mapping):
                 original[key] = update_nested(orig_val, val)
             else:
                 original[key] = val
diff --git a/altair/vegalite/v3/api.py b/altair/vegalite/v3/api.py
index 42d9b599..20c7c9d9 100644
--- a/altair/vegalite/v3/api.py
+++ b/altair/vegalite/v3/api.py
@@ -63,7 +63,7 @@ def _consolidate_data(data, context):
     return data
 
 
-def _prepare_data(data, context):
+def _prepare_data(data, context=None):
     """Convert input data to data for use within schema
 
     Parameters
@@ -71,7 +71,7 @@ def _prepare_data(data, context):
     data :
         The input dataset in the form of a DataFrame, dictionary, altair data
         object, or other type that is recognized by the data transformers.
-    context : dict
+    context : dict (optional)
         The to_dict context in which the data is being prepared. This is used
         to keep track of information that needs to be passed up and down the
         recursive serialization routine, such as global named datasets.
@@ -88,7 +88,7 @@ def _prepare_data(data, context):
         data = core.UrlData(data)
 
     # consolidate inline data to top-level datasets
-    if data_transformers.consolidate_datasets:
+    if context is not None and data_transformers.consolidate_datasets:
         data = _consolidate_data(data, context)
 
     # if data is still not a recognized type, then return
@@ -107,8 +107,7 @@ class LookupData(core.LookupData):
     def to_dict(self, *args, **kwargs):
         """Convert the chart to a dictionary suitable for JSON export"""
         copy = self.copy(deep=False)
-        context = kwargs.get('context', {})
-        copy.data = _prepare_data(copy.data, context)
+        copy.data = _prepare_data(copy.data, kwargs.get('context'))
         return super(LookupData, copy).to_dict(*args, **kwargs)
 
 
diff --git a/altair/vegalite/v4/api.py b/altair/vegalite/v4/api.py
index 9357838a..f99f605f 100644
--- a/altair/vegalite/v4/api.py
+++ b/altair/vegalite/v4/api.py
@@ -63,7 +63,7 @@ def _consolidate_data(data, context):
     return data
 
 
-def _prepare_data(data, context):
+def _prepare_data(data, context=None):
     """Convert input data to data for use within schema
 
     Parameters
@@ -71,7 +71,7 @@ def _prepare_data(data, context):
     data :
         The input dataset in the form of a DataFrame, dictionary, altair data
         object, or other type that is recognized by the data transformers.
-    context : dict
+    context : dict (optional)
         The to_dict context in which the data is being prepared. This is used
         to keep track of information that needs to be passed up and down the
         recursive serialization routine, such as global named datasets.
@@ -88,7 +88,7 @@ def _prepare_data(data, context):
         data = core.UrlData(data)
 
     # consolidate inline data to top-level datasets
-    if data_transformers.consolidate_datasets:
+    if context is not None and data_transformers.consolidate_datasets:
         data = _consolidate_data(data, context)
 
     # if data is still not a recognized type, then return
@@ -107,8 +107,7 @@ class LookupData(core.LookupData):
     def to_dict(self, *args, **kwargs):
         """Convert the chart to a dictionary suitable for JSON export"""
         copy = self.copy(deep=False)
-        context = kwargs.get('context', {})
-        copy.data = _prepare_data(copy.data, context)
+        copy.data = _prepare_data(copy.data, kwargs.get('context'))
         return super(LookupData, copy).to_dict(*args, **kwargs)
 
 
diff --git a/doc/user_guide/marks.rst b/doc/user_guide/marks.rst
index 43512d3b..ff42e705 100644
--- a/doc/user_guide/marks.rst
+++ b/doc/user_guide/marks.rst
@@ -19,7 +19,6 @@ area        :meth:`~Chart.mark_area`      A filled area plot.
 bar         :meth:`~Chart.mark_bar`       A bar plot.                                          :ref:`gallery_simple_bar_chart`
 circle      :meth:`~Chart.mark_circle`    A scatter plot with filled circles.                  :ref:`gallery_one_dot_per_zipcode`
 geoshape    :meth:`~Chart.mark_geoshape`  A geographic shape                                   :ref:`gallery_choropleth`
-image       :meth:`~Chart.mark_image`     A scatter plot with image markers.                   :ref:`gallery_scatter_image_marks`
 line        :meth:`~Chart.mark_line`      A line plot.                                         :ref:`gallery_simple_line_chart`
 point       :meth:`~Chart.mark_point`     A scatter plot with configurable point shapes.       :ref:`gallery_scatter_linked_brush`
 rect        :meth:`~Chart.mark_rect`      A filled rectangle, used for heatmaps                :ref:`gallery_simple_heatmap`
diff --git a/doc/user_guide/transform.rst b/doc/user_guide/transform.rst
index c66a48f2..b1552363 100644
--- a/doc/user_guide/transform.rst
+++ b/doc/user_guide/transform.rst
@@ -1093,7 +1093,7 @@ Regression Transform
 
 The regression transform fits two-dimensional regression models to smooth and
 predict data. This transform can fit multiple models for input data (one per group) 
-and generates new data objects that represent points for summary trend lines.
+nd generates new data objects that represent points for summary trend lines.
 Alternatively, this transform can be used to generate a set of objects containing
 regression model parameters, one per group.
 


Test patch:
diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py
index e1b3ca8a..ac66f5b4 100644
--- a/altair/vegalite/v3/tests/test_api.py
+++ b/altair/vegalite/v3/tests/test_api.py
@@ -568,8 +568,7 @@ def test_LookupData():
     df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
     lookup = alt.LookupData(data=df, key='x')
 
-    with alt.data_transformers.enable(consolidate_datasets=False):
-        dct = lookup.to_dict()
+    dct = lookup.to_dict()
     assert dct['key'] == 'x'
     assert dct['data'] == {'values': [{'x': 1, 'y': 4},
                                       {'x': 2, 'y': 5},
diff --git a/altair/vegalite/v4/tests/test_api.py b/altair/vegalite/v4/tests/test_api.py
index 9f48e3cf..2ff2d6d5 100644
--- a/altair/vegalite/v4/tests/test_api.py
+++ b/altair/vegalite/v4/tests/test_api.py
@@ -601,8 +601,7 @@ def test_LookupData():
     df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
     lookup = alt.LookupData(data=df, key='x')
 
-    with alt.data_transformers.enable(consolidate_datasets=False):
-        dct = lookup.to_dict()
+    dct = lookup.to_dict()
     assert dct['key'] == 'x'
     assert dct['data'] == {'values': [{'x': 1, 'y': 4},
                                       {'x': 2, 'y': 5},
diff --git a/tools/schemapi/tests/test_decorator.py b/tools/schemapi/tests/test_decorator.py
index af044745..9cb8b758 100644
--- a/tools/schemapi/tests/test_decorator.py
+++ b/tools/schemapi/tests/test_decorator.py
@@ -36,11 +36,11 @@ def test_myschema_decorator():
     assert myschema.to_dict() == {'a': {'foo': 'bar'}, 'b': ['foo', 'bar']}
 
     assert MySchema.__doc__.startswith('MySchema schema wrapper')
-    argspec = inspect.getfullargspec(MySchema.__init__)
+    argspec = inspect.getargspec(MySchema.__init__)
     assert argspec.args == ['self', 'a' ,'b', 'a2', 'b2', 'c', 'd']
     assert argspec.defaults == 6 * (Undefined,)
     assert argspec.varargs is None
-    assert argspec.varkw == 'kwds'
+    assert argspec.keywords == 'kwds'
 
 
 def test_stringarray_decorator():
@@ -51,7 +51,7 @@ def test_stringarray_decorator():
     assert arr.to_dict() == ['a', 'b', 'c']
 
     assert arr.__doc__.startswith('StringArray schema wrapper')
-    argspec = inspect.getfullargspec(StringArray.__init__)
+    argspec = inspect.getargspec(StringArray.__init__)
     assert argspec.args == ['self']
     assert argspec.varargs == 'args'
-    assert argspec.varkw is None
+    assert argspec.keywords is None