# Buggy code
def transform_joinaggregate(self, joinaggregate=Undefined, groupby=Undefined, **kwargs):
    """
    Add a JoinAggregateTransform to the schema.

    Parameters
    ----------
    joinaggregate : List(:class:`JoinAggregateFieldDef`)
        The definition of the fields in the join aggregate, and what calculations to use.
    groupby : List(string)
        The data fields for partitioning the data objects into separate groups. If
        unspecified, all data points will be in a single group.
    **kwargs
        joinaggregates can also be passed by keyword argument; see Examples.

    Returns
    -------
    self : Chart object
        returns chart to allow for chaining

    Examples
    --------
    >>> import altair as alt
    >>> chart = alt.Chart().transform_joinaggregate(x='sum(y)')
    >>> chart.transform[0]
    JoinAggregateTransform({
      joinaggregate: [JoinAggregateFieldDef({
        as: FieldName('x'),
        field: FieldName('y'),
        op: AggregateOp('sum')
      })]
    })

    See Also
    --------
    alt.JoinAggregateTransform : underlying transform object
    """
    if joinaggregate is Undefined:
        joinaggregate = []
    for key, val in kwargs.items():
        parsed = utils.parse_shorthand(val)
        dct = {
            "as": key,
            "field": parsed.get("field", Undefined),
            "op": parsed.get("aggregate", Undefined),
        }
        joinaggregate.append(core.JoinAggregateFieldDef.from_dict(dct))
    return self._add_transform(
        core.JoinAggregateTransform(joinaggregate=joinaggregate, groupby=groupby)
    )


# Traceback (ValidationError)
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-142-2bf583dd0be9> in <module>()
5 ).transform_joinaggregate(
6     group_count='count()',
----> 7     groupby=['group']
8 ).transform_calculate(
9     normalized_a="datum.a / datum.group_count",

~/.virtualenvs/simulate/lib/python3.6/site-packages/altair/vegalite/v3/api.py in transform_joinaggregate(self, joinaggregate, groupby, **kwargs)
929                    'field': parsed.get('field', Undefined),
930                    'op': parsed.get('aggregate', Undefined)}
--> 931             joinaggregate.append(core.JoinAggregateFieldDef.from_dict(dct))
932         return self._add_transform(core.JoinAggregateTransform(
933             joinaggregate=joinaggregate, groupby=groupby

~/.virtualenvs/simulate/lib/python3.6/site-packages/altair/utils/schemapi.py in from_dict(cls, dct, validate, _wrapper_classes)
366         """
367         if validate:
--> 368             cls.validate(dct)
369         if _wrapper_classes is None:
370             _wrapper_classes = cls._default_wrapper_classes()

~/.virtualenvs/simulate/lib/python3.6/site-packages/altair/utils/schemapi.py in validate(cls, instance, schema)
402             schema = cls._schema
403         resolver = jsonschema.RefResolver.from_schema(cls._rootschema or cls._schema)
--> 404         return jsonschema.validate(instance, schema, resolver=resolver)
405
406     @classmethod

~/.virtualenvs/simulate/lib/python3.6/site-packages/jsonschema/validators.py in validate(instance, schema, cls, *args, **kwargs)
897     error = exceptions.best_match(validator.iter_errors(instance))
898     if error is not None:
--> 899         raise error
900
901

ValidationError: Undefined is not of type 'string'

Failed validating 'type' in schema['properties']['field']:
{'type': 'string'}

On instance['field']:
Undefined

# Fixed code
def transform_joinaggregate(self, joinaggregate=Undefined, groupby=Undefined, **kwargs):
    """
    Add a JoinAggregateTransform to the schema.

    Parameters
    ----------
    joinaggregate : List(:class:`JoinAggregateFieldDef`)
        The definition of the fields in the join aggregate, and what calculations to use.
    groupby : List(string)
        The data fields for partitioning the data objects into separate groups. If
        unspecified, all data points will be in a single group.
    **kwargs
        joinaggregates can also be passed by keyword argument; see Examples.

    Returns
    -------
    self : Chart object
        returns chart to allow for chaining

    Examples
    --------
    >>> import altair as alt
    >>> chart = alt.Chart().transform_joinaggregate(x='sum(y)')
    >>> chart.transform[0]
    JoinAggregateTransform({
      joinaggregate: [JoinAggregateFieldDef({
        as: 'x',
        field: 'y',
        op: 'sum'
      })]
    })

    See Also
    --------
    alt.JoinAggregateTransform : underlying transform object
    """
    if joinaggregate is Undefined:
        joinaggregate = []
    for key, val in kwargs.items():
        parsed = utils.parse_shorthand(val)
        dct = {
            "as": key,
            "field": parsed.get("field", Undefined),
            "op": parsed.get("aggregate", Undefined),
        }
        joinaggregate.append(core.JoinAggregateFieldDef(**dct))
    return self._add_transform(
        core.JoinAggregateTransform(joinaggregate=joinaggregate, groupby=groupby)
    )