@@ -190,8 +190,12 @@ class Unlink(_DataType, namedtuple("Unlink", [])):
190
190
"""Unlink variable from its source, that is, remove compute_value"""
191
191
192
192
193
- Transform = Union [Rename , CategoriesMapping , Annotate , Unlink ]
194
- TransformTypes = (Rename , CategoriesMapping , Annotate , Unlink )
193
+ class StrpTime (_DataType , namedtuple ("StrpTime" , ["label" , "formats" , "have_date" , "have_time" ])):
194
+ """Use format on variable interpreted as time"""
195
+
196
+
197
+ Transform = Union [Rename , CategoriesMapping , Annotate , Unlink , StrpTime ]
198
+ TransformTypes = (Rename , CategoriesMapping , Annotate , Unlink , StrpTime )
195
199
196
200
CategoricalTransformTypes = (CategoriesMapping , Unlink )
197
201
@@ -1519,8 +1523,37 @@ class ContinuousVariableEditor(VariableEditor):
1519
1523
1520
1524
1521
1525
class TimeVariableEditor (VariableEditor ):
1522
- # TODO: enable editing of display format...
1523
- pass
1526
+ def __init__ (self , parent = None , ** kwargs ):
1527
+ super ().__init__ (parent , ** kwargs )
1528
+ form = self .layout ().itemAt (0 )
1529
+
1530
+ self .format_cb = QComboBox ()
1531
+ for item , data in [("Detect automatically" , (None , 1 , 1 ))] + list (
1532
+ Orange .data .TimeVariable .ADDITIONAL_FORMATS .items ()
1533
+ ):
1534
+ self .format_cb .addItem (item , StrpTime (item , * data ))
1535
+ self .format_cb .currentIndexChanged .connect (self .variable_changed )
1536
+ form .insertRow (2 , "Format:" , self .format_cb )
1537
+
1538
+ def set_data (self , var , transform = ()):
1539
+ super ().set_data (var , transform )
1540
+ if self .parent () is not None and isinstance (self .parent ().var , Time ):
1541
+ # when transforming from time to time disable format selection combo
1542
+ self .format_cb .setEnabled (False )
1543
+ else :
1544
+ # select the format from StrpTime transform
1545
+ for tr in transform :
1546
+ if isinstance (tr , StrpTime ):
1547
+ index = self .format_cb .findText (tr .label )
1548
+ self .format_cb .setCurrentIndex (index )
1549
+ self .format_cb .setEnabled (True )
1550
+
1551
+ def get_data (self ):
1552
+ var , tr = super ().get_data ()
1553
+ if var is not None and (self .parent () is None or not isinstance (self .parent ().var , Time )):
1554
+ # do not add StrpTime when transforming from time to time
1555
+ tr .insert (0 , self .format_cb .currentData ())
1556
+ return var , tr
1524
1557
1525
1558
1526
1559
def variable_icon (var ):
@@ -2581,14 +2614,17 @@ def apply_transform_time(var, trs):
2581
2614
def apply_transform_string (var , trs ):
2582
2615
# type: (Orange.data.StringVariable, List[Transform]) -> Orange.data.Variable
2583
2616
name , annotations = var .name , var .attributes
2617
+ out_type = Orange .data .StringVariable
2618
+ compute_value = Identity
2584
2619
for tr in trs :
2585
2620
if isinstance (tr , Rename ):
2586
2621
name = tr .name
2587
2622
elif isinstance (tr , Annotate ):
2588
2623
annotations = _parse_attributes (tr .annotations )
2589
- variable = Orange .data .StringVariable (
2590
- name = name , compute_value = Identity (var )
2591
- )
2624
+ elif isinstance (tr , StrpTime ):
2625
+ out_type = partial (Orange .data .TimeVariable , have_date = tr .have_date , have_time = tr .have_time )
2626
+ compute_value = partial (ReparseTimeTransform , tr = tr )
2627
+ variable = out_type (name = name , compute_value = compute_value (var ))
2592
2628
variable .attributes .update (annotations )
2593
2629
return variable
2594
2630
@@ -2649,21 +2685,6 @@ def mapper(arr, out=None, dtype=dtype, **kwargs):
2649
2685
return mapper
2650
2686
2651
2687
2652
- def time_parse (values : Sequence [str ], name = "__" ):
2653
- tvar = Orange .data .TimeVariable (name )
2654
- parse_time = ftry (tvar .parse , ValueError , np .nan )
2655
- _values = [parse_time (v ) for v in values ]
2656
- if np .all (np .isnan (_values )):
2657
- # try parsing it with pandas (like in transform)
2658
- dti = pd .to_datetime (values , errors = "coerce" )
2659
- _values = datetime_to_epoch (dti )
2660
- date_only = getattr (dti , "_is_dates_only" , False )
2661
- if np .all (dti != pd .NaT ):
2662
- tvar .have_date = True
2663
- tvar .have_time = not date_only
2664
- return tvar , _values
2665
-
2666
-
2667
2688
as_string = np .frompyfunc (str , 1 , 1 )
2668
2689
parse_float = ftry (float , ValueError , float ("nan" ))
2669
2690
@@ -2710,24 +2731,16 @@ def apply_reinterpret_d(var, tr, data):
2710
2731
# type: (Orange.data.DiscreteVariable, ReinterpretTransform, ndarray) -> Orange.data.Variable
2711
2732
if isinstance (tr , AsCategorical ):
2712
2733
return var
2713
- elif isinstance (tr , AsString ):
2734
+ elif isinstance (tr , (AsString , AsTime )):
2735
+ # TimeVar will be interpreted by StrpTime later
2714
2736
f = Lookup (var , np .array (var .values , dtype = object ), unknown = "" )
2715
- rvar = Orange .data .StringVariable (
2716
- name = var .name , compute_value = f
2717
- )
2737
+ rvar = Orange .data .StringVariable (name = var .name , compute_value = f )
2718
2738
elif isinstance (tr , AsContinuous ):
2719
2739
f = Lookup (var , np .array (list (map (parse_float , var .values ))),
2720
2740
unknown = np .nan )
2721
2741
rvar = Orange .data .ContinuousVariable (
2722
2742
name = var .name , compute_value = f , sparse = var .sparse
2723
2743
)
2724
- elif isinstance (tr , AsTime ):
2725
- _tvar , values = time_parse (var .values )
2726
- f = Lookup (var , np .array (values ), unknown = np .nan )
2727
- rvar = Orange .data .TimeVariable (
2728
- name = var .name , have_date = _tvar .have_date ,
2729
- have_time = _tvar .have_time , compute_value = f ,
2730
- )
2731
2744
else :
2732
2745
assert False
2733
2746
return copy_attributes (rvar , var )
@@ -2753,14 +2766,11 @@ def apply_reinterpret_c(var, tr, data: MArray):
2753
2766
elif isinstance (tr , AsContinuous ):
2754
2767
return var
2755
2768
elif isinstance (tr , AsString ):
2769
+ # TimeVar will be interpreted by StrpTime later
2756
2770
tstr = ToStringTransform (var )
2757
- rvar = Orange .data .StringVariable (
2758
- name = var .name , compute_value = tstr
2759
- )
2771
+ rvar = Orange .data .StringVariable (name = var .name , compute_value = tstr )
2760
2772
elif isinstance (tr , AsTime ):
2761
- rvar = Orange .data .TimeVariable (
2762
- name = var .name , compute_value = Identity (var )
2763
- )
2773
+ rvar = Orange .data .TimeVariable (name = var .name , compute_value = Identity (var ))
2764
2774
else :
2765
2775
assert False
2766
2776
return copy_attributes (rvar , var )
@@ -2783,14 +2793,9 @@ def apply_reinterpret_s(var: Orange.data.StringVariable, tr, data: MArray):
2783
2793
rvar = Orange .data .ContinuousVariable (
2784
2794
var .name , compute_value = ToContinuousTransform (var )
2785
2795
)
2786
- elif isinstance (tr , AsString ):
2796
+ elif isinstance (tr , (AsString , AsTime )):
2797
+ # TimeVar will be interpreted by StrpTime later
2787
2798
return var
2788
- elif isinstance (tr , AsTime ):
2789
- tvar , _ = time_parse (np .unique (data .data [~ data .mask ]))
2790
- rvar = Orange .data .TimeVariable (
2791
- name = var .name , have_date = tvar .have_date , have_time = tvar .have_time ,
2792
- compute_value = ReparseTimeTransform (var )
2793
- )
2794
2799
else :
2795
2800
assert False
2796
2801
return copy_attributes (rvar , var )
@@ -2822,6 +2827,7 @@ def apply_reinterpret_t(var: Orange.data.TimeVariable, tr, data):
2822
2827
else :
2823
2828
assert False
2824
2829
return copy_attributes (rvar , var )
2830
+ #todo: disable format dropdown when allready time
2825
2831
2826
2832
2827
2833
def orange_isna (variable : Orange .data .Variable , data : ndarray ) -> ndarray :
@@ -2867,23 +2873,28 @@ def transform(self, c):
2867
2873
raise TypeError
2868
2874
2869
2875
2870
- def datetime_to_epoch (dti : pd .DatetimeIndex ) -> np .ndarray :
2876
+ def datetime_to_epoch (dti : pd .DatetimeIndex , only_time ) -> np .ndarray :
2871
2877
"""Convert datetime to epoch"""
2872
- data = dti .values .astype ("M8[us]" )
2873
- mask = np .isnat (data )
2874
- data = data .astype (float ) / 1e6
2875
- data [mask ] = np .nan
2876
- return data
2878
+ delta = dti - (dti .normalize () if only_time else pd .Timestamp ("1970-01-01" ))
2879
+ return (delta / pd .Timedelta ("1s" )).values
2877
2880
2878
2881
2879
2882
class ReparseTimeTransform (Transformation ):
2880
2883
"""
2881
2884
Re-parse the column's string repr as datetime.
2882
2885
"""
2886
+ def __init__ (self , variable , tr ):
2887
+ super ().__init__ (variable )
2888
+ self .tr = tr
2889
+
2883
2890
def transform (self , c ):
2884
- c = column_str_repr (self .variable , c )
2885
- c = pd .to_datetime (c , errors = "coerce" )
2886
- return datetime_to_epoch (c )
2891
+ # if self.formats is none guess format option is selected
2892
+ formats = self .tr .formats if self .tr .formats is not None else [None ]
2893
+ for f in formats :
2894
+ d = pd .to_datetime (c , errors = "coerce" , format = f )
2895
+ if pd .notnull (d ).any ():
2896
+ return datetime_to_epoch (d , only_time = not self .tr .have_date )
2897
+ return np .nan
2887
2898
2888
2899
2889
2900
class LookupMappingTransform (Transformation ):
0 commit comments