mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-08 10:05:23 +08:00
ENH: Fail fast on outputs in CustomClassifier.
We don't support multiple outputs for CustomClassifier because we use LabelArrays for string classifiers.
This commit is contained in:
@@ -16,6 +16,7 @@ from zipline.errors import (
|
||||
)
|
||||
from zipline.pipeline import (
|
||||
Classifier,
|
||||
CustomClassifier,
|
||||
CustomFactor,
|
||||
Factor,
|
||||
Filter,
|
||||
@@ -25,8 +26,10 @@ from zipline.pipeline.data import Column, DataSet
|
||||
from zipline.pipeline.data.testing import TestingDataSet
|
||||
from zipline.pipeline.term import AssetExists, NotSpecified
|
||||
from zipline.pipeline.expression import NUMEXPR_MATH_FUNCS
|
||||
from zipline.testing import parameter_space
|
||||
from zipline.utils.numpy_utils import (
|
||||
bool_dtype,
|
||||
categorical_dtype,
|
||||
complex128_dtype,
|
||||
datetime64ns_dtype,
|
||||
float64_dtype,
|
||||
@@ -569,3 +572,33 @@ class SubDataSetTestCase(TestCase):
|
||||
'subclass column %r should have the same dtype as the parent' %
|
||||
k,
|
||||
)
|
||||
|
||||
@parameter_space(
|
||||
dtype_=[categorical_dtype, int64_dtype],
|
||||
outputs_=[('a',), ('a', 'b')],
|
||||
)
|
||||
def test_reject_multi_output_classifiers(self, dtype_, outputs_):
|
||||
"""
|
||||
Multi-output CustomClassifiers don't work because they use special
|
||||
output allocation for string arrays.
|
||||
"""
|
||||
|
||||
class SomeClassifier(CustomClassifier):
|
||||
dtype = dtype_
|
||||
window_length = 5
|
||||
inputs = [SomeDataSet.foo, SomeDataSet.bar]
|
||||
outputs = outputs_
|
||||
missing_value = dtype_.type('123')
|
||||
|
||||
expected_error = (
|
||||
"SomeClassifier does not support custom outputs, "
|
||||
"but received custom outputs={outputs}.".format(outputs=outputs_)
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as e:
|
||||
SomeClassifier()
|
||||
self.assertEqual(str(e.exception), expected_error)
|
||||
|
||||
with self.assertRaises(ValueError) as e:
|
||||
SomeClassifier()
|
||||
self.assertEqual(str(e.exception), expected_error)
|
||||
|
||||
@@ -25,6 +25,7 @@ from ..mixins import (
|
||||
PositiveWindowLengthMixin,
|
||||
RestrictedDTypeMixin,
|
||||
SingleInputMixin,
|
||||
StandardOutputs,
|
||||
)
|
||||
|
||||
|
||||
@@ -351,16 +352,35 @@ class StringPredicate(SingleInputMixin, Filter):
|
||||
)
|
||||
|
||||
|
||||
class CustomClassifier(PositiveWindowLengthMixin, CustomTermMixin, Classifier):
|
||||
class CustomClassifier(PositiveWindowLengthMixin,
|
||||
StandardOutputs,
|
||||
CustomTermMixin,
|
||||
Classifier):
|
||||
"""
|
||||
Base class for user-defined Classifiers.
|
||||
|
||||
Does not suppport multiple outputs.
|
||||
|
||||
See Also
|
||||
--------
|
||||
zipline.pipeline.CustomFactor
|
||||
zipline.pipeline.CustomFilter
|
||||
"""
|
||||
pass
|
||||
def _allocate_output(self, windows, shape):
|
||||
"""
|
||||
Override the default array allocation to produce a LabelArray when we
|
||||
have a string-like dtype.
|
||||
"""
|
||||
if self.dtype == int64_dtype:
|
||||
return super(CustomClassifier, self)._allocate_output(
|
||||
windows,
|
||||
shape,
|
||||
)
|
||||
|
||||
# This is a little bit of a hack. We might not know what the
|
||||
# categories for a LabelArray are until it's actually been loaded, so
|
||||
# we need to look at the underlying data.
|
||||
return windows[0].data.empty_like(shape)
|
||||
|
||||
|
||||
class Latest(LatestMixin, CustomClassifier):
|
||||
@@ -373,18 +393,7 @@ class Latest(LatestMixin, CustomClassifier):
|
||||
zipline.pipeline.factors.factor.Latest
|
||||
zipline.pipeline.filters.filter.Latest
|
||||
"""
|
||||
def _allocate_output(self, windows, shape):
|
||||
"""
|
||||
Override the default array allocation to produce a LabelArray when we
|
||||
have a string-like dtype.
|
||||
"""
|
||||
if self.dtype == int64_dtype:
|
||||
return super(Latest, self)._allocate_output(windows, shape)
|
||||
|
||||
# This is a little bit of a hack. We might not know what the
|
||||
# categories for a LabelArray are until it's actually been loaded, so
|
||||
# we need to look at the underlying data.
|
||||
return windows[0].data.empty_like(shape)
|
||||
pass
|
||||
|
||||
|
||||
class InvalidClassifierComparison(TypeError):
|
||||
|
||||
@@ -36,6 +36,22 @@ class SingleInputMixin(object):
|
||||
)
|
||||
|
||||
|
||||
class StandardOutputs(object):
|
||||
"""
|
||||
Validation mixin enforcing that a Term cannot produce non-standard outputs.
|
||||
"""
|
||||
def _validate(self):
|
||||
super(StandardOutputs, self)._validate()
|
||||
if self.outputs is not NotSpecified:
|
||||
raise ValueError(
|
||||
"{typename} does not support custom outputs,"
|
||||
" but received custom outputs={outputs}.".format(
|
||||
typename=type(self).__name__,
|
||||
outputs=self.outputs,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class RestrictedDTypeMixin(object):
|
||||
"""
|
||||
Validation mixin enforcing that a term has a specific dtype.
|
||||
|
||||
Reference in New Issue
Block a user