Repository: altair-viz/altair
Pull request #646 (2018-03-24 21:41:18)

Simplify ``count()`` aggregation
Addresses #640


Patch:
diff --git a/altair/utils/core.py b/altair/utils/core.py
index 63945401..2585ce2d 100644
--- a/altair/utils/core.py
+++ b/altair/utils/core.py
@@ -114,7 +114,7 @@ def sanitize_dataframe(df):
     return df
 
 
-def parse_shorthand(shorthand):
+def _parse_shorthand(shorthand):
     """
     Parse the shorthand expression for aggregation, field, and type.
 
@@ -148,8 +148,11 @@ def parse_shorthand(shorthand):
     # build regular expressions
     units = dict(field='(?P<field>.*)',
                  type='(?P<type>{0})'.format('|'.join(valid_typecodes)),
+                 count='(?P<aggregate>count)',
                  aggregate='(?P<aggregate>{0})'.format('|'.join(valid_aggregates)))
-    patterns = [r'{aggregate}\({field}\):{type}',
+    patterns = [r'{count}\(\)',
+                r'{count}\(\):{type}',
+                r'{aggregate}\({field}\):{type}',
                 r'{aggregate}\({field}\)',
                 r'{field}:{type}',
                 r'{field}']
@@ -165,17 +168,31 @@ def parse_shorthand(shorthand):
     if type_:
         match['type'] = INV_TYPECODE_MAP.get(type_, type_)
 
+    # counts are quantitative by default
+    if match == {'aggregate': 'count'}:
+        match['type'] = 'quantitative'
+
     return match
 
 
-def parse_shorthand_plus_data(shorthand, data):
-    """Parse a field shorthand, and use data to infer type if not specified
+def parse_shorthand(shorthand, data=None):
+    """Parse the shorthand expression for aggregation, field, and type.
+
+    These are of the form:
+
+    - "col_name"
+    - "col_name:O"
+    - "average(col_name)"
+    - "average(col_name):O"
+
+    Optionally, a dataframe may be supplied, from which the type
+    will be inferred if not specified in the shorthand.
 
     Parameters
     ----------
     shorthand: str
         Shorthand string of the form "agg(col):typ"
-    data : pd.DataFrame
+    data : pd.DataFrame (optional)
         Dataframe from which to infer types
 
     Returns
@@ -188,34 +205,38 @@ def parse_shorthand_plus_data(shorthand, data):
     --------
     >>> data = pd.DataFrame({'foo': ['A', 'B', 'A', 'B'],
     ...                      'bar': [1, 2, 3, 4]})
-    ...
 
-    >>> parse_shorthand_plus_data('foo', data)
+    >>> parse_shorthand('name')
+    {'field': 'name'}
+
+    >>> parse_shorthand('average(col)')
+    {'field': 'col', 'aggregate': 'average'}
+
+    >>> parse_shorthand('foo:O')
+    {'field': 'foo', 'type': 'ordinal'}
+
+    >>> parse_shorthand('min(foo):Q')
+    {'field': 'foo', 'aggregate': 'min', 'type': 'ordinal'}
+
+    >>> parse_shorthand('foo', data)
     {'field': 'foo', 'type': 'nominal'}
 
-    >>> parse_shorthand_plus_data('bar', data)
+    >>> parse_shorthand('bar', data)
     {'field': 'bar', 'type': 'quantitative'}
 
-    >>> parse_shorthand_plus_data('bar:O', data)
+    >>> parse_shorthand('bar:O', data)
     {'field': 'bar', 'type': 'ordinal'}
 
-    >>> parse_shorthand_plus_data('sum(bar)', data)
+    >>> parse_shorthand('sum(bar)', data)
     {'aggregate': 'sum', 'field': 'bar', 'type': 'quantitative'}
+
+    >>> parse_shorthand('count()', data)
+    {'aggregate': 'count', 'bar', 'type': 'quantitative'}
     """
-    attrs = parse_shorthand(shorthand)
-    if 'type' not in attrs and attrs['field'] != '*':
-        if not isinstance(data, pd.DataFrame):
-            raise ValueError("'{0}' encoding field is specified without a type, "
-                             "the type cannot be automacially inferred because "
-                             "the data is not specified as a pandas.DataFrame."
-                             "".format(attrs["field"]))
-        if attrs['field'] not in data.columns:
-            raise ValueError("'{0}' encoding field is specified without a type, "
-                             "and the type cannot be automatically inferred "
-                             "because it does not match any column names within "
-                             "the data. Valid columns are {1}"
-                             "".format(attrs["field"], list(data.columns)))
-        attrs['type'] = infer_vegalite_type(data[attrs['field']])
+    attrs = _parse_shorthand(shorthand)
+    if isinstance(data, pd.DataFrame) and 'type' not in attrs:
+        if 'field' in attrs and attrs['field'] in data.columns:
+            attrs['type'] = infer_vegalite_type(data[attrs['field']])
     return attrs
 
 
diff --git a/altair/vegalite/v1/schema/channels.py b/altair/vegalite/v1/schema/channels.py
index a4bb6dc1..c2abd520 100644
--- a/altair/vegalite/v1/schema/channels.py
+++ b/altair/vegalite/v1/schema/channels.py
@@ -3,17 +3,20 @@
 
 import six
 from . import core
+import pandas as pd
 from altair.utils.schemapi import Undefined
-from altair.utils import parse_shorthand, parse_shorthand_plus_data
+from altair.utils import parse_shorthand
 
 
 class Row(core.PositionChannelDef):
     """Row schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -42,24 +45,42 @@ class Row(core.PositionChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, axis=Undefined, bin=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Row, self).__init__(field=field, aggregate=aggregate, axis=axis, bin=bin, scale=scale,
-                                  sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
-                                  **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Row, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                  field=field, scale=scale, sort=sort, timeUnit=timeUnit, title=title,
+                                  type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Row, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -68,10 +89,12 @@ class Row(core.PositionChannelDef):
 class Column(core.PositionChannelDef):
     """Column schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -100,24 +123,42 @@ class Column(core.PositionChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, axis=Undefined, bin=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Column, self).__init__(field=field, aggregate=aggregate, axis=axis, bin=bin, scale=scale,
-                                     sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
-                                     **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Column, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                     field=field, scale=scale, sort=sort, timeUnit=timeUnit,
+                                     title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Column, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -126,10 +167,12 @@ class Column(core.PositionChannelDef):
 class X(core.PositionChannelDef):
     """X schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -158,24 +201,42 @@ class X(core.PositionChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, axis=Undefined, bin=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(X, self).__init__(field=field, aggregate=aggregate, axis=axis, bin=bin, scale=scale,
-                                sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
-                                **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(X, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                field=field, scale=scale, sort=sort, timeUnit=timeUnit, title=title,
+                                type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(X, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -184,10 +245,12 @@ class X(core.PositionChannelDef):
 class Y(core.PositionChannelDef):
     """Y schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -216,24 +279,42 @@ class Y(core.PositionChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, axis=Undefined, bin=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Y, self).__init__(field=field, aggregate=aggregate, axis=axis, bin=bin, scale=scale,
-                                sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
-                                **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Y, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                field=field, scale=scale, sort=sort, timeUnit=timeUnit, title=title,
+                                type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Y, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -242,10 +323,12 @@ class Y(core.PositionChannelDef):
 class X2(core.FieldDef):
     """X2 schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -268,22 +351,40 @@ class X2(core.FieldDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, timeUnit=Undefined, title=Undefined,
-                 type=Undefined, value=Undefined, **kwds):
-        super(X2, self).__init__(field=field, aggregate=aggregate, bin=bin, timeUnit=timeUnit,
-                                 title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined, **kwds):
+        super(X2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                 timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(X2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -292,10 +393,12 @@ class X2(core.FieldDef):
 class Y2(core.FieldDef):
     """Y2 schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -318,22 +421,40 @@ class Y2(core.FieldDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, timeUnit=Undefined, title=Undefined,
-                 type=Undefined, value=Undefined, **kwds):
-        super(Y2, self).__init__(field=field, aggregate=aggregate, bin=bin, timeUnit=timeUnit,
-                                 title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined, **kwds):
+        super(Y2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                 timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Y2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -342,10 +463,12 @@ class Y2(core.FieldDef):
 class Color(core.ChannelDefWithLegend):
     """Color schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -374,24 +497,42 @@ class Color(core.ChannelDefWithLegend):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, legend=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Color, self).__init__(field=field, aggregate=aggregate, bin=bin, legend=legend,
-                                    scale=scale, sort=sort, timeUnit=timeUnit, title=title, type=type,
-                                    value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Color, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                    legend=legend, scale=scale, sort=sort, timeUnit=timeUnit,
+                                    title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Color, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -400,10 +541,12 @@ class Color(core.ChannelDefWithLegend):
 class Opacity(core.ChannelDefWithLegend):
     """Opacity schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -432,24 +575,42 @@ class Opacity(core.ChannelDefWithLegend):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, legend=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Opacity, self).__init__(field=field, aggregate=aggregate, bin=bin, legend=legend,
-                                      scale=scale, sort=sort, timeUnit=timeUnit, title=title, type=type,
-                                      value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Opacity, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                      legend=legend, scale=scale, sort=sort, timeUnit=timeUnit,
+                                      title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Opacity, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -458,10 +619,12 @@ class Opacity(core.ChannelDefWithLegend):
 class Size(core.ChannelDefWithLegend):
     """Size schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -490,24 +653,42 @@ class Size(core.ChannelDefWithLegend):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, legend=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Size, self).__init__(field=field, aggregate=aggregate, bin=bin, legend=legend,
-                                   scale=scale, sort=sort, timeUnit=timeUnit, title=title, type=type,
-                                   value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Size, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                   legend=legend, scale=scale, sort=sort, timeUnit=timeUnit,
+                                   title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Size, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -516,10 +697,12 @@ class Size(core.ChannelDefWithLegend):
 class Shape(core.ChannelDefWithLegend):
     """Shape schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -548,24 +731,42 @@ class Shape(core.ChannelDefWithLegend):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, legend=Undefined, scale=Undefined,
-                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
-                 **kwds):
-        super(Shape, self).__init__(field=field, aggregate=aggregate, bin=bin, legend=legend,
-                                    scale=scale, sort=sort, timeUnit=timeUnit, title=title, type=type,
-                                    value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, title=Undefined,
+                 type=Undefined, value=Undefined, **kwds):
+        super(Shape, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                    legend=legend, scale=scale, sort=sort, timeUnit=timeUnit,
+                                    title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Shape, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -574,10 +775,12 @@ class Shape(core.ChannelDefWithLegend):
 class Detail(core.FieldDef):
     """Detail schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -600,22 +803,40 @@ class Detail(core.FieldDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, timeUnit=Undefined, title=Undefined,
-                 type=Undefined, value=Undefined, **kwds):
-        super(Detail, self).__init__(field=field, aggregate=aggregate, bin=bin, timeUnit=timeUnit,
-                                     title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined, **kwds):
+        super(Detail, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                     timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Detail, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -624,10 +845,12 @@ class Detail(core.FieldDef):
 class Text(core.FieldDef):
     """Text schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -650,22 +873,40 @@ class Text(core.FieldDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, timeUnit=Undefined, title=Undefined,
-                 type=Undefined, value=Undefined, **kwds):
-        super(Text, self).__init__(field=field, aggregate=aggregate, bin=bin, timeUnit=timeUnit,
-                                   title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined, **kwds):
+        super(Text, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                   timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Text, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -674,10 +915,12 @@ class Text(core.FieldDef):
 class Label(core.FieldDef):
     """Label schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -700,22 +943,40 @@ class Label(core.FieldDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, timeUnit=Undefined, title=Undefined,
-                 type=Undefined, value=Undefined, **kwds):
-        super(Label, self).__init__(field=field, aggregate=aggregate, bin=bin, timeUnit=timeUnit,
-                                    title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined, **kwds):
+        super(Label, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                    timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Label, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -724,10 +985,12 @@ class Label(core.FieldDef):
 class Path(core.OrderChannelDef):
     """Path schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -752,22 +1015,42 @@ class Path(core.OrderChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, sort=Undefined, timeUnit=Undefined,
-                 title=Undefined, type=Undefined, value=Undefined, **kwds):
-        super(Path, self).__init__(field=field, aggregate=aggregate, bin=bin, sort=sort,
-                                   timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
+                 **kwds):
+        super(Path, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                   sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
+                                   **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Path, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -776,10 +1059,12 @@ class Path(core.OrderChannelDef):
 class Order(core.OrderChannelDef):
     """Order schema wrapper
     
-    Mapping(required=[])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : AggregateOp
         Aggregation function for the field  (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).
@@ -804,22 +1089,42 @@ class Order(core.OrderChannelDef):
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, aggregate=Undefined, bin=Undefined, sort=Undefined, timeUnit=Undefined,
-                 title=Undefined, type=Undefined, value=Undefined, **kwds):
-        super(Order, self).__init__(field=field, aggregate=aggregate, bin=bin, sort=sort,
-                                    timeUnit=timeUnit, title=title, type=type, value=value, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 sort=Undefined, timeUnit=Undefined, title=Undefined, type=Undefined, value=Undefined,
+                 **kwds):
+        super(Order, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                    sort=sort, timeUnit=timeUnit, title=title, type=type, value=value,
+                                    **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Order, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
diff --git a/altair/vegalite/v2/api.py b/altair/vegalite/v2/api.py
index 366f7bba..2b61e5a7 100644
--- a/altair/vegalite/v2/api.py
+++ b/altair/vegalite/v2/api.py
@@ -265,7 +265,7 @@ def condition(predicate, if_true, if_false, **kwargs):
         # dict in the appropriate schema
         if_true = if_true.to_dict()
     elif isinstance(if_true, six.string_types):
-        if_true = {'field': if_true}
+        if_true = {'shorthand': if_true}
         if_true.update(kwargs)
     condition.update(if_true)
 
@@ -275,7 +275,7 @@ def condition(predicate, if_true, if_false, **kwargs):
         selection = if_false.copy()
         selection.condition = condition
     elif isinstance(if_false, six.string_types):
-        selection = dict(condition=condition, field=if_false)
+        selection = {'condition': condition, 'shorthand': if_false}
         selection.update(kwargs)
     else:
         selection = dict(condition=condition, **if_false)
@@ -726,12 +726,7 @@ class Chart(TopLevelMixin, mixins.MarkMethodMixin, core.TopLevelFacetedUnitSpec)
                 return obj
 
             if isinstance(obj, six.string_types):
-                obj = {'field': obj}
-
-            # if obj is not a string or Schema, it must be a mapping
-            if 'field' in obj:
-                obj = obj.copy()
-                obj.update(utils.parse_shorthand(obj['field']))
+                obj = {'shorthand': obj}
 
             if 'value' in obj:
                 clsname += 'Value'
@@ -749,15 +744,16 @@ class Chart(TopLevelMixin, mixins.MarkMethodMixin, core.TopLevelFacetedUnitSpec)
                 # our attempts at finding the correct class have failed
                 return obj
 
-        for prop, field in list(kwargs.items()):
+        for prop, obj in list(kwargs.items()):
             try:
-                condition = field['condition']
+                condition = obj['condition']
             except (KeyError, TypeError):
                 pass
             else:
                 if condition is not Undefined:
-                    field['condition'] = _wrap_in_channel_class(condition, prop)
-            kwargs[prop] = _wrap_in_channel_class(field, prop)
+                    obj['condition'] = _wrap_in_channel_class(condition, prop)
+            kwargs[prop] = _wrap_in_channel_class(obj, prop)
+
 
         copy = self.copy(deep=True, ignore=['data'])
 
diff --git a/altair/vegalite/v2/examples/binned_scatterplot.py b/altair/vegalite/v2/examples/binned_scatterplot.py
index 9832902e..f079c4e2 100644
--- a/altair/vegalite/v2/examples/binned_scatterplot.py
+++ b/altair/vegalite/v2/examples/binned_scatterplot.py
@@ -12,5 +12,5 @@ source = data.movies.url
 chart = alt.Chart(source).mark_circle().encode(
     alt.X('IMDB_Rating:Q', bin=True),
     alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
-    size='count(*):Q'
+    size='count()'
 )
diff --git a/altair/vegalite/v2/examples/histogram.py b/altair/vegalite/v2/examples/histogram.py
index 0eb7d25b..909e54e0 100644
--- a/altair/vegalite/v2/examples/histogram.py
+++ b/altair/vegalite/v2/examples/histogram.py
@@ -13,5 +13,5 @@ movies = data.movies.url
 
 chart = alt.Chart(movies).mark_bar().encode(
     alt.X("IMDB_Rating:Q", bin=True),
-    y='count(*):Q',
+    y='count()',
 )
diff --git a/altair/vegalite/v2/examples/histogram_with_a_global_mean_overlay.py b/altair/vegalite/v2/examples/histogram_with_a_global_mean_overlay.py
index aac8a61d..90843de3 100644
--- a/altair/vegalite/v2/examples/histogram_with_a_global_mean_overlay.py
+++ b/altair/vegalite/v2/examples/histogram_with_a_global_mean_overlay.py
@@ -11,7 +11,7 @@ source = data.movies.url
 
 bar = alt.Chart(source).mark_bar().encode(
     alt.X('IMDB_Rating:Q', bin=True, axis=None),
-    alt.Y('count(*):Q')
+    alt.Y('count()')
 )
 
 rule = alt.Chart(source).mark_rule(color='red').encode(
diff --git a/altair/vegalite/v2/examples/interactive_cross_highlight.py b/altair/vegalite/v2/examples/interactive_cross_highlight.py
index df7e2927..fa9c7d14 100644
--- a/altair/vegalite/v2/examples/interactive_cross_highlight.py
+++ b/altair/vegalite/v2/examples/interactive_cross_highlight.py
@@ -15,7 +15,7 @@ pts = alt.selection(type="single", encodings=['x'])
 rect = alt.Chart(data.movies.url).mark_rect().encode(
     alt.X('IMDB_Rating:Q', bin=True),
     alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
-    alt.Color('count(*):Q',
+    alt.Color('count()',
         scale=alt.Scale(scheme='greenblue'),
         legend=alt.Legend(title='Total Records')
     )
@@ -23,7 +23,7 @@ rect = alt.Chart(data.movies.url).mark_rect().encode(
 
 circ = rect.mark_point().encode(
     alt.ColorValue('grey'),
-    alt.Size('count(*):Q',
+    alt.Size('count()',
         legend=alt.Legend(title='Records in Selection')
     )
 ).transform_filter(
@@ -32,7 +32,7 @@ circ = rect.mark_point().encode(
 
 bar = alt.Chart(data.movies.url).mark_bar().encode(
     x='Major_Genre:N',
-    y='count(*):Q',
+    y='count()',
     color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
 ).properties(
     selection=pts,
diff --git a/altair/vegalite/v2/examples/interactive_layered_crossfilter.py b/altair/vegalite/v2/examples/interactive_layered_crossfilter.py
index 3c96f6af..9ffdfd39 100644
--- a/altair/vegalite/v2/examples/interactive_layered_crossfilter.py
+++ b/altair/vegalite/v2/examples/interactive_layered_crossfilter.py
@@ -19,7 +19,7 @@ brush = alt.selection(type='interval', encodings=['x'])
 # background and highlights
 base = alt.Chart().mark_bar().encode(
     x=alt.X(alt.repeat('column'), type='quantitative', bin=alt.Bin(maxbins=20)),
-    y='count(*):Q'
+    y='count()'
 ).properties(
     width=180,
     height=130
diff --git a/altair/vegalite/v2/examples/layered_heatmap_text.py b/altair/vegalite/v2/examples/layered_heatmap_text.py
index 8824dd78..51f3b9cc 100644
--- a/altair/vegalite/v2/examples/layered_heatmap_text.py
+++ b/altair/vegalite/v2/examples/layered_heatmap_text.py
@@ -14,13 +14,13 @@ cars = data.cars.url
 heatmap = alt.Chart(cars).mark_rect().encode(
     alt.X('Cylinders:O', scale=alt.Scale(paddingInner=0)),
     alt.Y('Origin:O', scale=alt.Scale(paddingInner=0)),
-    color='count(*):Q'
+    color='count()'
 )
 
 text = alt.Chart(cars).mark_text(baseline='middle').encode(
     x='Cylinders:O',
     y='Origin:O',
-    text='count(*):Q',
+    text='count()',
     color=alt.condition(datum['count_*'] > 100,
                         alt.value('black'),
                         alt.value('white'))
diff --git a/altair/vegalite/v2/examples/layered_histogram.py b/altair/vegalite/v2/examples/layered_histogram.py
index bbc3597b..a96870d0 100644
--- a/altair/vegalite/v2/examples/layered_histogram.py
+++ b/altair/vegalite/v2/examples/layered_histogram.py
@@ -25,7 +25,7 @@ chart = alt.Chart(df).mark_area(
     interpolate='step'
 ).encode(
     alt.X('Measurement', bin=alt.Bin(maxbins=100)),
-    alt.Y('count(*):Q', stack=None),
+    alt.Y('count()', stack=None),
     alt.Color(
         'Experiment',
         scale=alt.Scale(range=['#0000ff', '#008000', '#ff0000'])
diff --git a/altair/vegalite/v2/examples/seattle_weather_interactive.py b/altair/vegalite/v2/examples/seattle_weather_interactive.py
index 812170b8..9ec0de3c 100644
--- a/altair/vegalite/v2/examples/seattle_weather_interactive.py
+++ b/altair/vegalite/v2/examples/seattle_weather_interactive.py
@@ -39,7 +39,7 @@ points = alt.Chart().mark_point().encode(
 
 # Bottom panel is a bar chart of weather type
 bars = alt.Chart().mark_bar().encode(
-    x='count(*):Q',
+    x='count()',
     y='weather:N',
     color=alt.condition(click, color, alt.value('lightgray')),
 ).transform_filter(
diff --git a/altair/vegalite/v2/examples/stacked_bar_chart.py b/altair/vegalite/v2/examples/stacked_bar_chart.py
index 323200ab..4ebcd3bf 100644
--- a/altair/vegalite/v2/examples/stacked_bar_chart.py
+++ b/altair/vegalite/v2/examples/stacked_bar_chart.py
@@ -22,5 +22,5 @@ chart = alt.Chart(weather).mark_bar().encode(
         axis=alt.Axis(title='Month of the Year'),
         timeUnit='month',
     ),
-    y='count(*):Q',
+    y='count()',
 )
diff --git a/altair/vegalite/v2/examples/trellis_histogram.py b/altair/vegalite/v2/examples/trellis_histogram.py
index 2b87e94c..45acdd52 100644
--- a/altair/vegalite/v2/examples/trellis_histogram.py
+++ b/altair/vegalite/v2/examples/trellis_histogram.py
@@ -10,6 +10,6 @@ cars = alt.load_dataset('cars')
 
 chart = alt.Chart(cars).mark_bar().encode(
     alt.X("Horsepower:Q", bin=True),
-    y='count(*):Q',
+    y='count()',
     row='Origin'
 )
diff --git a/altair/vegalite/v2/schema/channels.py b/altair/vegalite/v2/schema/channels.py
index dbcce5fb..26492d41 100644
--- a/altair/vegalite/v2/schema/channels.py
+++ b/altair/vegalite/v2/schema/channels.py
@@ -3,14 +3,15 @@
 
 import six
 from . import core
+import pandas as pd
 from altair.utils.schemapi import Undefined
-from altair.utils import parse_shorthand, parse_shorthand_plus_data
+from altair.utils import parse_shorthand
 
 
 class Color(core.MarkPropFieldDefWithCondition):
     """Color schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -20,11 +21,8 @@ class Color(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -71,26 +69,50 @@ class Color(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Color, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                    condition=condition, legend=legend, scale=scale, sort=sort,
-                                    timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Color, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                    condition=condition, field=field, legend=legend, scale=scale,
+                                    sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Color, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -114,6 +136,8 @@ class ColorValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(ColorValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -125,10 +149,7 @@ class ColorValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(ColorValue, copy).to_dict(validate=validate,
@@ -139,15 +160,12 @@ class ColorValue(core.MarkPropValueDefWithCondition):
 class Column(core.FacetFieldDef):
     """Column schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -175,25 +193,48 @@ class Column(core.FacetFieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, header=Undefined,
-                 sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Column, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                     header=header, sort=sort, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 header=Undefined, sort=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Column, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                     header=header, sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Column, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -202,16 +243,13 @@ class Column(core.FacetFieldDef):
 class Detail(core.FieldDef):
     """Detail schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -235,25 +273,48 @@ class Detail(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Detail, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                     timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Detail, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                     timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Detail, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -262,7 +323,7 @@ class Detail(core.FieldDef):
 class Fill(core.MarkPropFieldDefWithCondition):
     """Fill schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -272,11 +333,8 @@ class Fill(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -323,26 +381,50 @@ class Fill(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Fill, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                   condition=condition, legend=legend, scale=scale, sort=sort,
-                                   timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Fill, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                   condition=condition, field=field, legend=legend, scale=scale,
+                                   sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Fill, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -366,6 +448,8 @@ class FillValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(FillValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -377,10 +461,7 @@ class FillValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(FillValue, copy).to_dict(validate=validate,
@@ -391,7 +472,7 @@ class FillValue(core.MarkPropValueDefWithCondition):
 class Href(core.FieldDefWithCondition):
     """Href schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -401,11 +482,8 @@ class Href(core.FieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -434,25 +512,49 @@ class Href(core.FieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 timeUnit=Undefined, **kwds):
-        super(Href, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                   condition=condition, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Href, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                   condition=condition, field=field, timeUnit=timeUnit, type=type,
+                                   **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Href, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -475,6 +577,8 @@ class HrefValue(core.ValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(HrefValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -486,10 +590,7 @@ class HrefValue(core.ValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(HrefValue, copy).to_dict(validate=validate,
@@ -500,16 +601,13 @@ class HrefValue(core.ValueDefWithCondition):
 class Key(core.FieldDef):
     """Key schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -533,25 +631,48 @@ class Key(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Key, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                  timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Key, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                  timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Key, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -560,16 +681,13 @@ class Key(core.FieldDef):
 class Latitude(core.FieldDef):
     """Latitude schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -593,25 +711,48 @@ class Latitude(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Latitude, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                       timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Latitude, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                       timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Latitude, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -620,16 +761,13 @@ class Latitude(core.FieldDef):
 class Latitude2(core.FieldDef):
     """Latitude2 schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -653,25 +791,48 @@ class Latitude2(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Latitude2, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                        timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Latitude2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                        timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Latitude2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -680,16 +841,13 @@ class Latitude2(core.FieldDef):
 class Longitude(core.FieldDef):
     """Longitude schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -713,25 +871,48 @@ class Longitude(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Longitude, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                        timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Longitude, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                        timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Longitude, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -740,16 +921,13 @@ class Longitude(core.FieldDef):
 class Longitude2(core.FieldDef):
     """Longitude2 schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -773,25 +951,48 @@ class Longitude2(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Longitude2, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                         timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Longitude2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                         timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Longitude2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -800,7 +1001,7 @@ class Longitude2(core.FieldDef):
 class Opacity(core.MarkPropFieldDefWithCondition):
     """Opacity schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -810,11 +1011,8 @@ class Opacity(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -861,26 +1059,50 @@ class Opacity(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Opacity, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                      condition=condition, legend=legend, scale=scale, sort=sort,
-                                      timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Opacity, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                      condition=condition, field=field, legend=legend, scale=scale,
+                                      sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Opacity, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -904,6 +1126,8 @@ class OpacityValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(OpacityValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -915,10 +1139,7 @@ class OpacityValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(OpacityValue, copy).to_dict(validate=validate,
@@ -929,15 +1150,12 @@ class OpacityValue(core.MarkPropValueDefWithCondition):
 class Order(core.OrderFieldDef):
     """Order schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -963,25 +1181,48 @@ class Order(core.OrderFieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, sort=Undefined,
-                 timeUnit=Undefined, **kwds):
-        super(Order, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin, sort=sort,
-                                    timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 sort=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Order, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                    sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Order, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -990,15 +1231,12 @@ class Order(core.OrderFieldDef):
 class Row(core.FacetFieldDef):
     """Row schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1026,25 +1264,48 @@ class Row(core.FacetFieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, header=Undefined,
-                 sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Row, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin, header=header,
-                                  sort=sort, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 header=Undefined, sort=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Row, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                  header=header, sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Row, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1053,7 +1314,7 @@ class Row(core.FacetFieldDef):
 class Shape(core.MarkPropFieldDefWithCondition):
     """Shape schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -1063,11 +1324,8 @@ class Shape(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1114,26 +1372,50 @@ class Shape(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Shape, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                    condition=condition, legend=legend, scale=scale, sort=sort,
-                                    timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Shape, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                    condition=condition, field=field, legend=legend, scale=scale,
+                                    sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Shape, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1157,6 +1439,8 @@ class ShapeValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(ShapeValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -1168,10 +1452,7 @@ class ShapeValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(ShapeValue, copy).to_dict(validate=validate,
@@ -1182,7 +1463,7 @@ class ShapeValue(core.MarkPropValueDefWithCondition):
 class Size(core.MarkPropFieldDefWithCondition):
     """Size schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -1192,11 +1473,8 @@ class Size(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1243,26 +1521,50 @@ class Size(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Size, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                   condition=condition, legend=legend, scale=scale, sort=sort,
-                                   timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Size, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                   condition=condition, field=field, legend=legend, scale=scale,
+                                   sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Size, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1286,6 +1588,8 @@ class SizeValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(SizeValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -1297,10 +1601,7 @@ class SizeValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(SizeValue, copy).to_dict(validate=validate,
@@ -1311,7 +1612,7 @@ class SizeValue(core.MarkPropValueDefWithCondition):
 class Stroke(core.MarkPropFieldDefWithCondition):
     """Stroke schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -1321,11 +1622,8 @@ class Stroke(core.MarkPropFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1372,26 +1670,50 @@ class Stroke(core.MarkPropFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined, **kwds):
-        super(Stroke, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                     condition=condition, legend=legend, scale=scale, sort=sort,
-                                     timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, legend=Undefined, scale=Undefined, sort=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Stroke, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                     condition=condition, field=field, legend=legend, scale=scale,
+                                     sort=sort, timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Stroke, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1415,6 +1737,8 @@ class StrokeValue(core.MarkPropValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(StrokeValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -1426,10 +1750,7 @@ class StrokeValue(core.MarkPropValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(StrokeValue, copy).to_dict(validate=validate,
@@ -1440,7 +1761,7 @@ class StrokeValue(core.MarkPropValueDefWithCondition):
 class Text(core.TextFieldDefWithCondition):
     """Text schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -1450,11 +1771,8 @@ class Text(core.TextFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1486,25 +1804,49 @@ class Text(core.TextFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 format=Undefined, timeUnit=Undefined, **kwds):
-        super(Text, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                   condition=condition, format=format, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, format=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Text, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                   condition=condition, field=field, format=format, timeUnit=timeUnit,
+                                   type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Text, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1527,6 +1869,8 @@ class TextValue(core.TextValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(TextValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -1538,10 +1882,7 @@ class TextValue(core.TextValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(TextValue, copy).to_dict(validate=validate,
@@ -1552,7 +1893,7 @@ class TextValue(core.TextValueDefWithCondition):
 class Tooltip(core.TextFieldDefWithCondition):
     """Tooltip schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     A FieldDef with Condition<ValueDef>
     {
        condition: {value: ...},
@@ -1562,11 +1903,8 @@ class Tooltip(core.TextFieldDefWithCondition):
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1598,25 +1936,49 @@ class Tooltip(core.TextFieldDefWithCondition):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
-                 format=Undefined, timeUnit=Undefined, **kwds):
-        super(Tooltip, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                      condition=condition, format=format, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, condition=Undefined,
+                 field=Undefined, format=Undefined, timeUnit=Undefined, type=Undefined, **kwds):
+        super(Tooltip, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin,
+                                      condition=condition, field=field, format=format,
+                                      timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Tooltip, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1639,6 +2001,8 @@ class TooltipValue(core.TextValueDefWithCondition):
     value : anyOf(float, string, boolean)
         A constant value in visual domain.
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, condition=Undefined, **kwds):
         super(TooltipValue, self).__init__(value=value, condition=condition, **kwds)
 
@@ -1650,10 +2014,7 @@ class TooltipValue(core.TextValueDefWithCondition):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(TooltipValue, copy).to_dict(validate=validate,
@@ -1664,15 +2025,12 @@ class TooltipValue(core.TextValueDefWithCondition):
 class X(core.PositionFieldDef):
     """X schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1733,25 +2091,50 @@ class X(core.PositionFieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
-                 scale=Undefined, sort=Undefined, stack=Undefined, timeUnit=Undefined, **kwds):
-        super(X, self).__init__(field=field, type=type, aggregate=aggregate, axis=axis, bin=bin,
-                                scale=scale, sort=sort, stack=stack, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, stack=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(X, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                field=field, scale=scale, sort=sort, stack=stack, timeUnit=timeUnit,
+                                type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(X, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1769,6 +2152,8 @@ class XValue(core.ValueDef):
         A constant value in visual domain (e.g., `"red"` / "#0099ff" for color, values 
         between `0` to `1` for opacity).
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, **kwds):
         super(XValue, self).__init__(value=value, **kwds)
 
@@ -1780,10 +2165,7 @@ class XValue(core.ValueDef):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(XValue, copy).to_dict(validate=validate,
@@ -1794,16 +2176,13 @@ class XValue(core.ValueDef):
 class X2(core.FieldDef):
     """X2 schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1827,25 +2206,48 @@ class X2(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(X2, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                 timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(X2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                 timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(X2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1863,6 +2265,8 @@ class X2Value(core.ValueDef):
         A constant value in visual domain (e.g., `"red"` / "#0099ff" for color, values 
         between `0` to `1` for opacity).
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, **kwds):
         super(X2Value, self).__init__(value=value, **kwds)
 
@@ -1874,10 +2278,7 @@ class X2Value(core.ValueDef):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(X2Value, copy).to_dict(validate=validate,
@@ -1888,15 +2289,12 @@ class X2Value(core.ValueDef):
 class Y(core.PositionFieldDef):
     """Y schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -1957,25 +2355,50 @@ class Y(core.PositionFieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
-                 scale=Undefined, sort=Undefined, stack=Undefined, timeUnit=Undefined, **kwds):
-        super(Y, self).__init__(field=field, type=type, aggregate=aggregate, axis=axis, bin=bin,
-                                scale=scale, sort=sort, stack=stack, timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, axis=Undefined, bin=Undefined,
+                 field=Undefined, scale=Undefined, sort=Undefined, stack=Undefined, timeUnit=Undefined,
+                 type=Undefined, **kwds):
+        super(Y, self).__init__(shorthand=shorthand, aggregate=aggregate, axis=axis, bin=bin,
+                                field=field, scale=scale, sort=sort, stack=stack, timeUnit=timeUnit,
+                                type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Y, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -1993,6 +2416,8 @@ class YValue(core.ValueDef):
         A constant value in visual domain (e.g., `"red"` / "#0099ff" for color, values 
         between `0` to `1` for opacity).
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, **kwds):
         super(YValue, self).__init__(value=value, **kwds)
 
@@ -2004,10 +2429,7 @@ class YValue(core.ValueDef):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(YValue, copy).to_dict(validate=validate,
@@ -2018,16 +2440,13 @@ class YValue(core.ValueDef):
 class Y2(core.FieldDef):
     """Y2 schema wrapper
     
-    Mapping(required=[type])
+    Mapping(required=[shorthand])
     Definition object for a data field, its type and transformation of an encoding channel.
     
     Attributes
     ----------
-    type : Type
-        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
-        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
-        `"longitude"`, and `"geojson"`) when a [geographic 
-        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
+    shorthand : string
+        shorthand for field, aggregate, and type
     aggregate : Aggregate
         Aggregation function for the field (e.g., `mean`, `sum`, `median`, `min`, `max`, 
         `count`).  __Default value:__ `undefined` (None)
@@ -2051,25 +2470,48 @@ class Y2(core.FieldDef):
         temporal field that gets casted as 
         ordinal](https://vega.github.io/vega-lite/docs/type.html#cast).  __Default value:__ 
         `undefined` (None)
+    type : Type
+        The encoded field's type of measurement (`"quantitative"`, `"temporal"`, 
+        `"ordinal"`, or `"nominal"`). It can also be a geo type (`"latitude"`, 
+        `"longitude"`, and `"geojson"`) when a [geographic 
+        projection](https://vega.github.io/vega-lite/docs/projection.html) is applied.
     """
     _class_is_valid_at_instantiation = False
 
-    def __init__(self, field, type=Undefined, aggregate=Undefined, bin=Undefined, timeUnit=Undefined,
-                 **kwds):
-        super(Y2, self).__init__(field=field, type=type, aggregate=aggregate, bin=bin,
-                                 timeUnit=timeUnit, **kwds)
+    def __init__(self, shorthand=Undefined, aggregate=Undefined, bin=Undefined, field=Undefined,
+                 timeUnit=Undefined, type=Undefined, **kwds):
+        super(Y2, self).__init__(shorthand=shorthand, aggregate=aggregate, bin=bin, field=field,
+                                 timeUnit=timeUnit, type=type, **kwds)
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{0} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {0}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {'field': self.shorthand}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined})
         return super(Y2, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -2087,6 +2529,8 @@ class Y2Value(core.ValueDef):
         A constant value in visual domain (e.g., `"red"` / "#0099ff" for color, values 
         between `0` to `1` for opacity).
     """
+    _class_is_valid_at_instantiation = False
+
     def __init__(self, value, **kwds):
         super(Y2Value, self).__init__(value=value, **kwds)
 
@@ -2098,10 +2542,7 @@ class Y2Value(core.ValueDef):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super(Y2Value, copy).to_dict(validate=validate,
diff --git a/doc/case_studies/exploring-weather.rst b/doc/case_studies/exploring-weather.rst
index 105b1e94..f672c13c 100644
--- a/doc/case_studies/exploring-weather.rst
+++ b/doc/case_studies/exploring-weather.rst
@@ -56,7 +56,7 @@ The result is a histogram of precipitation values:
 
     alt.Chart(df).mark_bar().encode(
         alt.X('precipitation', bin=True),
-        alt.Y('count(*):Q')
+        alt.Y('count()')
     )
 
 Next, let’s look at how precipitation in Seattle changes throughout the year.
@@ -142,7 +142,7 @@ stack the bars atop each other:
 
     alt.Chart(df).mark_bar().encode(
         x=alt.X('date:N', timeUnit='month'),
-        y='count(*):Q',
+        y='count()',
         color='weather',
     )
 
@@ -165,7 +165,7 @@ meaning of the plot more clear:
 
     alt.Chart(df).mark_bar().encode(
         x=alt.X('date:N', timeUnit='month', axis=alt.Axis(title='Month of the year')),
-        y='count(*):Q',
+        y='count()',
         color=alt.Color('weather', legend=alt.Legend(title='Weather type'), scale=scale),
     )
 
@@ -200,7 +200,7 @@ by weather type:
 .. altair-plot::
 
     alt.Chart(df).mark_bar().encode(
-        x='count(*):Q',
+        x='count()',
         y='weather:N',
         color=alt.Color('weather:N', scale=scale),
     )
@@ -227,7 +227,7 @@ of the selection:
     )
 
     bars = alt.Chart().mark_bar().encode(
-        x='count(*):Q',
+        x='count()',
         y='weather:N',
         color=alt.Color('weather:N', scale=scale),
     ).transform_calculate(
diff --git a/doc/user_guide/compound_charts.rst b/doc/user_guide/compound_charts.rst
index bfd2b6fc..2cac33e0 100644
--- a/doc/user_guide/compound_charts.rst
+++ b/doc/user_guide/compound_charts.rst
@@ -93,7 +93,7 @@ distribution of its points:
     )
 
     chart2 = alt.Chart(iris).mark_bar().encode(
-        x='count(*):Q',
+        x='count()',
         y=alt.Y('petalWidth:Q', bin=alt.Bin(maxbins=30)),
         color='species:N'
     ).properties(
diff --git a/doc/user_guide/encoding.rst b/doc/user_guide/encoding.rst
index 9cf89243..e07c08be 100644
--- a/doc/user_guide/encoding.rst
+++ b/doc/user_guide/encoding.rst
@@ -264,14 +264,14 @@ In Altair, such an operation looks like this:
 
    alt.Chart(cars).mark_bar().encode(
        alt.X('Horsepower', bin=True),
-       y='count(*):Q'
+       y='count()'
        # could also use alt.Y(aggregate='count', type='quantitative')
    )
 
 Notice here we use the shorthand version of expressing an encoding channel
 (see :ref:`shorthand-description`) with the ``count`` aggregation,
-the special ``*`` wild-card identifier often used with counts,
-and ``Q`` for quantitative type.
+which is the one aggregation that does not require a field to be
+specified.
 
 Similarly, we can create a two-dimensional histogram using, for example, the
 size of points to indicate counts within the grid (sometimes called
@@ -282,7 +282,7 @@ a "Bubble Plot"):
    alt.Chart(cars).mark_point().encode(
        alt.X('Horsepower', bin=True),
        alt.Y('Miles_per_Gallon', bin=True),
-       size='count(*):Q',
+       size='count()',
    )
 
 There is no need, however, to limit aggregations to counts alone. For example,
@@ -294,7 +294,7 @@ represents the mean of a third quantity, such as acceleration:
    alt.Chart(cars).mark_circle().encode(
        alt.X('Horsepower', bin=True),
        alt.Y('Miles_per_Gallon', bin=True),
-       size='count(*):Q',
+       size='count()',
        color='average(Acceleration):Q'
    )
 
@@ -348,5 +348,5 @@ Shorthand            Equivalent long-form
 ``x='name:Q'``       ``alt.X('name', type='quantitative')``
 ``x='sum(name)'``    ``alt.X('name', aggregate='sum')``
 ``x='sum(name):Q'``  ``alt.X('name', aggregate='sum', type='quantitative')``
-``x='sum(*):Q'``     ``alt.X(aggregate='sum', type='quantitative')``
+``x='count():Q'``    ``alt.X(aggregate='count', type='quantitative')``
 ===================  =======================================================
diff --git a/doc/user_guide/selections.rst b/doc/user_guide/selections.rst
index 24e801c4..1c73dea7 100644
--- a/doc/user_guide/selections.rst
+++ b/doc/user_guide/selections.rst
@@ -140,7 +140,7 @@ selection:
         return alt.Chart(cars).mark_rect().encode(
             x="Cylinders:O",
             y="Origin:N",
-            color=alt.condition(selector, 'count(*):Q', alt.value('lightgray'))
+            color=alt.condition(selector, 'count()', alt.value('lightgray'))
         ).properties(
             width=300,
             height=180,
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index cec5e73a..824c7842 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -1,4 +1,5 @@
 """Generate a schema wrapper from a schema"""
+import copy
 import os
 import sys
 import json
@@ -54,16 +55,34 @@ class {classname}(core.{basename}):
     {init_code}
 
     def to_dict(self, validate=True, ignore=(), context=None):
-        type_ = getattr(self, 'type', Undefined)
         context = context or {{}}
-        if not isinstance(self.field, six.string_types):
-            # field is a RepeatSpec or similar; cannot infer type
+        if self.shorthand is Undefined:
             kwds = {{}}
-        elif type_ is Undefined and 'data' in context:
-            kwds = parse_shorthand_plus_data(self.field, context['data'])
+        elif isinstance(self.shorthand, six.string_types):
+            kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
+            type_defined = self._kwds.get('type', Undefined) is not Undefined
+            if not (type_defined or 'type' in kwds):
+                if isinstance(context.get('data', None), pd.DataFrame):
+                    raise ValueError("{{0}} encoding field is specified without a type; "
+                                     "the type cannot be inferred because it does not "
+                                     "match any column in the data.".format(self.shorthand))
+                else:
+                    raise ValueError("{{0}} encoding field is specified without a type; "
+                                     "the type cannot be automacially inferred because "
+                                     "the data is not specified as a pandas.DataFrame."
+                                     "".format(self.shorthand))
         else:
-            kwds = parse_shorthand(self.field)
-        self._kwds.update(kwds)
+            # shorthand is not a string; we pass the definition to field
+            if self.field is not Undefined:
+                raise ValueError("both shorthand and field specified in {{0}}"
+                                 "".format(self.__class__.__name__))
+            # field is a RepeatSpec or similar; cannot infer type
+            kwds = {{'field': self.shorthand}}
+
+        # set shorthand to Undefined, because it's not part of the schema
+        self.shorthand = Undefined
+        self._kwds.update({{k: v for k, v in kwds.items()
+                           if self._kwds.get(k, Undefined) is Undefined}})
         return super({classname}, self).to_dict(validate=validate,
                                                 ignore=ignore,
                                                 context=context)
@@ -72,6 +91,8 @@ class {classname}(core.{basename}):
 VALUE_TEMPLATE = '''
 class {classname}(core.{basename}):
     """{docstring}"""
+    _class_is_valid_at_instantiation = False
+
     {init_code}
 
     def to_dict(self, validate=True, ignore=(), context=None):
@@ -82,10 +103,7 @@ class {classname}(core.{basename}):
             if isinstance(condition, core.SchemaBase):
                 pass
             elif 'field' in condition and 'type' not in condition:
-                if 'data' in context:
-                    kwds = parse_shorthand_plus_data(condition['field'], context['data'])
-                else:
-                    kwds = parse_shorthand(condition['field'])
+                kwds = parse_shorthand(condition['field'], context.get('data', None))
                 copy = self.copy()
                 copy.condition.update(kwds)
         return super({classname}, copy).to_dict(validate=validate,
@@ -199,8 +217,9 @@ def generate_vegalite_channel_wrappers(schemafile, imports=None,
     if imports is None:
         imports = ["import six",
                    "from . import core",
+                   "import pandas as pd",
                    "from altair.utils.schemapi import Undefined",
-                   "from altair.utils import parse_shorthand, parse_shorthand_plus_data"]
+                   "from altair.utils import parse_shorthand"]
     contents = [HEADER]
     contents.extend(imports)
     contents.append('')
@@ -226,7 +245,11 @@ def generate_vegalite_channel_wrappers(schemafile, imports=None,
                 nodefault = ['value']
             else:
                 template = FIELD_TEMPLATE
-                nodefault = ['field']
+                nodefault = []
+                defschema = copy.deepcopy(schema['definitions'][basename])
+                defschema['properties']['shorthand'] = {'type': 'string',
+                                                        'description': 'shorthand for field, aggregate, and type'}
+                defschema['required'] = ['shorthand']
             docstring = codegen.docstring(classname=classname, schema=defschema,
                                           rootschema=schema, indent=4)
             init_code = codegen.init_code(classname=classname, schema=defschema,


Test patch:
diff --git a/altair/utils/tests/test_core.py b/altair/utils/tests/test_core.py
index b2b597f1..77af6ccd 100644
--- a/altair/utils/tests/test_core.py
+++ b/altair/utils/tests/test_core.py
@@ -5,7 +5,7 @@ import json
 import numpy as np
 import pandas as pd
 
-from .. import parse_shorthand, parse_shorthand_plus_data, update_nested
+from .. import parse_shorthand, update_nested
 
 
 def test_parse_shorthand():
@@ -43,10 +43,14 @@ def test_parse_shorthand():
     check('average(a b:(c\nd):Q', aggregate='average',
           field='a b:(c\nd', type='quantitative')
 
+    # special case: count doesn't need an argument
+    check('count()', aggregate='count', type='quantitative')
+    check('count():O', aggregate='count', type='ordinal')
 
-def test_parse_shorthand_plus_data():
+
+def test_parse_shorthand_with_data():
     def check(s, data, **kwargs):
-        assert parse_shorthand_plus_data(s, data) == kwargs
+        assert parse_shorthand(s, data) == kwargs
 
     data = pd.DataFrame({'x': [1, 2, 3, 4, 5],
                          'y': ['A', 'B', 'C', 'D', 'E'],