Skip to content

Commit cba88ed

Browse files
committed
Merge pull request pandas-dev#4660 from jreback/hdf_empty
BUG: GH4273, appending a 0-len table to HDFStore was raising
2 parents f41e929 + 62ca5f7 commit cba88ed

File tree

3 files changed

+86
-41
lines changed

3 files changed

+86
-41
lines changed

doc/source/release.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ See :ref:`Internal Refactoring<whatsnew_0130.refactoring>`
197197
- raising an invalid ``TypeError`` rather than ``ValueError`` when appending
198198
with a different block ordering (:issue:`4096`)
199199
- ``read_hdf`` was not respecting as passed ``mode`` (:issue:`4504`)
200-
- Fixed bug in tslib.tz_convert(vals, tz1, tz2): it could raise IndexError exception while
200+
- appending a 0-len table will work correctly (:issue:`4273`)
201+
- Fixed bug in tslib.tz_convert(vals, tz1, tz2): it could raise IndexError exception while
201202
trying to access trans[pos + 1] (:issue:`4496`)
202203
- The ``by`` argument now works correctly with the ``layout`` argument
203204
(:issue:`4102`, :issue:`4014`) in ``*.hist`` plotting methods

pandas/io/pytables.py

+6
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,11 @@ def _write_to_group(self, key, value, index=True, table=False, append=False,
950950
self._handle.removeNode(group, recursive=True)
951951
group = None
952952

953+
# we don't want to store a table node at all if are object is 0-len
954+
# as there are not dtypes
955+
if getattr(value,'empty',None) and (table or append):
956+
return
957+
953958
if group is None:
954959
paths = key.split('/')
955960

@@ -982,6 +987,7 @@ def _write_to_group(self, key, value, index=True, table=False, append=False,
982987
if not s.is_table and complib:
983988
raise ValueError('Compression not supported on non-table')
984989

990+
# write the object
985991
s.write(obj = value, append=append, complib=complib, **kwargs)
986992
if s.is_table and index:
987993
s.create_index(columns = index)

pandas/io/tests/test_pytables.py

+78-40
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
AttributeConflictWarning, DuplicateWarning,
1818
PossibleDataLossError, ClosedFileError)
1919
import pandas.util.testing as tm
20-
from pandas.tests.test_series import assert_series_equal
21-
from pandas.tests.test_frame import assert_frame_equal
20+
from pandas.util.testing import (assert_panel4d_equal,
21+
assert_panel_equal,
22+
assert_frame_equal,
23+
assert_series_equal)
2224
from pandas import concat, Timestamp
2325
from pandas import compat
2426

@@ -134,7 +136,7 @@ def roundtrip(key, obj,**kwargs):
134136
assert_frame_equal(o, roundtrip('frame',o))
135137

136138
o = tm.makePanel()
137-
tm.assert_panel_equal(o, roundtrip('panel',o))
139+
assert_panel_equal(o, roundtrip('panel',o))
138140

139141
# table
140142
df = DataFrame(dict(A=lrange(5), B=lrange(5)))
@@ -521,22 +523,22 @@ def test_append(self):
521523
_maybe_remove(store, 'wp1')
522524
store.append('wp1', wp.ix[:, :10, :])
523525
store.append('wp1', wp.ix[:, 10:, :])
524-
tm.assert_panel_equal(store['wp1'], wp)
526+
assert_panel_equal(store['wp1'], wp)
525527

526528
# ndim
527529
p4d = tm.makePanel4D()
528530
_maybe_remove(store, 'p4d')
529531
store.append('p4d', p4d.ix[:, :, :10, :])
530532
store.append('p4d', p4d.ix[:, :, 10:, :])
531-
tm.assert_panel4d_equal(store['p4d'], p4d)
533+
assert_panel4d_equal(store['p4d'], p4d)
532534

533535
# test using axis labels
534536
_maybe_remove(store, 'p4d')
535537
store.append('p4d', p4d.ix[:, :, :10, :], axes=[
536538
'items', 'major_axis', 'minor_axis'])
537539
store.append('p4d', p4d.ix[:, :, 10:, :], axes=[
538540
'items', 'major_axis', 'minor_axis'])
539-
tm.assert_panel4d_equal(store['p4d'], p4d)
541+
assert_panel4d_equal(store['p4d'], p4d)
540542

541543
# test using differnt number of items on each axis
542544
p4d2 = p4d.copy()
@@ -545,15 +547,15 @@ def test_append(self):
545547
_maybe_remove(store, 'p4d2')
546548
store.append(
547549
'p4d2', p4d2, axes=['items', 'major_axis', 'minor_axis'])
548-
tm.assert_panel4d_equal(store['p4d2'], p4d2)
550+
assert_panel4d_equal(store['p4d2'], p4d2)
549551

550552
# test using differt order of items on the non-index axes
551553
_maybe_remove(store, 'wp1')
552554
wp_append1 = wp.ix[:, :10, :]
553555
store.append('wp1', wp_append1)
554556
wp_append2 = wp.ix[:, 10:, :].reindex(items=wp.items[::-1])
555557
store.append('wp1', wp_append2)
556-
tm.assert_panel_equal(store['wp1'], wp)
558+
assert_panel_equal(store['wp1'], wp)
557559

558560
# dtype issues - mizxed type in a single object column
559561
df = DataFrame(data=[[1, 2], [0, 1], [1, 2], [0, 0]])
@@ -757,15 +759,15 @@ def check_indexers(key, indexers):
757759
_maybe_remove(store, 'p4d')
758760
store.append('p4d', p4d.ix[:, :, :10, :], axes=indexers)
759761
store.append('p4d', p4d.ix[:, :, 10:, :])
760-
tm.assert_panel4d_equal(store.select('p4d'), p4d)
762+
assert_panel4d_equal(store.select('p4d'), p4d)
761763
check_indexers('p4d', indexers)
762764

763765
# same as above, but try to append with differnt axes
764766
_maybe_remove(store, 'p4d')
765767
store.append('p4d', p4d.ix[:, :, :10, :], axes=indexers)
766768
store.append('p4d', p4d.ix[:, :, 10:, :], axes=[
767769
'labels', 'items', 'major_axis'])
768-
tm.assert_panel4d_equal(store.select('p4d'), p4d)
770+
assert_panel4d_equal(store.select('p4d'), p4d)
769771
check_indexers('p4d', indexers)
770772

771773
# pass incorrect number of axes
@@ -778,34 +780,34 @@ def check_indexers(key, indexers):
778780
_maybe_remove(store, 'p4d')
779781
store.append('p4d', p4d.ix[:, :, :10, :], axes=indexers)
780782
store.append('p4d', p4d.ix[:, :, 10:, :])
781-
tm.assert_panel4d_equal(store['p4d'], p4d)
783+
assert_panel4d_equal(store['p4d'], p4d)
782784
check_indexers('p4d', indexers)
783785

784786
# different than default indexables #2
785787
indexers = ['major_axis', 'labels', 'minor_axis']
786788
_maybe_remove(store, 'p4d')
787789
store.append('p4d', p4d.ix[:, :, :10, :], axes=indexers)
788790
store.append('p4d', p4d.ix[:, :, 10:, :])
789-
tm.assert_panel4d_equal(store['p4d'], p4d)
791+
assert_panel4d_equal(store['p4d'], p4d)
790792
check_indexers('p4d', indexers)
791793

792794
# partial selection
793795
result = store.select('p4d', ['labels=l1'])
794796
expected = p4d.reindex(labels=['l1'])
795-
tm.assert_panel4d_equal(result, expected)
797+
assert_panel4d_equal(result, expected)
796798

797799
# partial selection2
798800
result = store.select('p4d', [Term(
799801
'labels=l1'), Term('items=ItemA'), Term('minor_axis=B')])
800802
expected = p4d.reindex(
801803
labels=['l1'], items=['ItemA'], minor_axis=['B'])
802-
tm.assert_panel4d_equal(result, expected)
804+
assert_panel4d_equal(result, expected)
803805

804806
# non-existant partial selection
805807
result = store.select('p4d', [Term(
806808
'labels=l1'), Term('items=Item1'), Term('minor_axis=B')])
807809
expected = p4d.reindex(labels=['l1'], items=[], minor_axis=['B'])
808-
tm.assert_panel4d_equal(result, expected)
810+
assert_panel4d_equal(result, expected)
809811

810812
def test_append_with_strings(self):
811813

@@ -821,15 +823,15 @@ def check_col(key,name,size):
821823
store.append('s1', wp2)
822824
expected = concat([wp, wp2], axis=2)
823825
expected = expected.reindex(minor_axis=sorted(expected.minor_axis))
824-
tm.assert_panel_equal(store['s1'], expected)
826+
assert_panel_equal(store['s1'], expected)
825827
check_col('s1', 'minor_axis', 20)
826828

827829
# test dict format
828830
store.append('s2', wp, min_itemsize={'minor_axis': 20})
829831
store.append('s2', wp2)
830832
expected = concat([wp, wp2], axis=2)
831833
expected = expected.reindex(minor_axis=sorted(expected.minor_axis))
832-
tm.assert_panel_equal(store['s2'], expected)
834+
assert_panel_equal(store['s2'], expected)
833835
check_col('s2', 'minor_axis', 20)
834836

835837
# apply the wrong field (similar to #1)
@@ -1305,10 +1307,46 @@ def check(obj, comparator):
13051307
check(df, tm.assert_frame_equal)
13061308

13071309
p = tm.makePanel()
1308-
check(p, tm.assert_panel_equal)
1310+
check(p, assert_panel_equal)
13091311

13101312
p4d = tm.makePanel4D()
1311-
check(p4d, tm.assert_panel4d_equal)
1313+
check(p4d, assert_panel4d_equal)
1314+
1315+
# empty frame, GH4273
1316+
with ensure_clean(self.path) as store:
1317+
1318+
# 0 len
1319+
df_empty = DataFrame(columns=list('ABC'))
1320+
store.append('df',df_empty)
1321+
self.assertRaises(KeyError,store.select, 'df')
1322+
1323+
# repeated append of 0/non-zero frames
1324+
df = DataFrame(np.random.rand(10,3),columns=list('ABC'))
1325+
store.append('df',df)
1326+
assert_frame_equal(store.select('df'),df)
1327+
store.append('df',df_empty)
1328+
assert_frame_equal(store.select('df'),df)
1329+
1330+
# store
1331+
df = DataFrame(columns=list('ABC'))
1332+
store.put('df2',df)
1333+
assert_frame_equal(store.select('df2'),df)
1334+
1335+
# 0 len
1336+
p_empty = Panel(items=list('ABC'))
1337+
store.append('p',p_empty)
1338+
self.assertRaises(KeyError,store.select, 'p')
1339+
1340+
# repeated append of 0/non-zero frames
1341+
p = Panel(np.random.randn(3,4,5),items=list('ABC'))
1342+
store.append('p',p)
1343+
assert_panel_equal(store.select('p'),p)
1344+
store.append('p',p_empty)
1345+
assert_panel_equal(store.select('p'),p)
1346+
1347+
# store
1348+
store.put('p2',p_empty)
1349+
assert_panel_equal(store.select('p2'),p_empty)
13121350

13131351
def test_append_raise(self):
13141352

@@ -1433,7 +1471,7 @@ def test_table_mixed_dtypes(self):
14331471

14341472
with ensure_clean(self.path) as store:
14351473
store.append('p1_mixed', wp)
1436-
tm.assert_panel_equal(store.select('p1_mixed'), wp)
1474+
assert_panel_equal(store.select('p1_mixed'), wp)
14371475

14381476
# ndim
14391477
wp = tm.makePanel4D()
@@ -1447,7 +1485,7 @@ def test_table_mixed_dtypes(self):
14471485

14481486
with ensure_clean(self.path) as store:
14491487
store.append('p4d_mixed', wp)
1450-
tm.assert_panel4d_equal(store.select('p4d_mixed'), wp)
1488+
assert_panel4d_equal(store.select('p4d_mixed'), wp)
14511489

14521490
def test_unimplemented_dtypes_table_columns(self):
14531491

@@ -1595,7 +1633,7 @@ def test_remove_where(self):
15951633
store.remove('wp', [('minor_axis', ['A', 'D'])])
15961634
rs = store.select('wp')
15971635
expected = wp.reindex(minor_axis=['B', 'C'])
1598-
tm.assert_panel_equal(rs, expected)
1636+
assert_panel_equal(rs, expected)
15991637

16001638
# empty where
16011639
_maybe_remove(store, 'wp')
@@ -1630,7 +1668,7 @@ def test_remove_crit(self):
16301668
assert(n == 36)
16311669
result = store.select('wp3')
16321670
expected = wp.reindex(major_axis=wp.major_axis - date4)
1633-
tm.assert_panel_equal(result, expected)
1671+
assert_panel_equal(result, expected)
16341672

16351673
# upper half
16361674
store.put('wp', wp, table=True)
@@ -1647,7 +1685,7 @@ def test_remove_crit(self):
16471685

16481686
result = store['wp']
16491687
expected = wp.truncate(after=date).reindex(minor=['B', 'C'])
1650-
tm.assert_panel_equal(result, expected)
1688+
assert_panel_equal(result, expected)
16511689

16521690
# individual row elements
16531691
store.put('wp2', wp, table=True)
@@ -1657,30 +1695,30 @@ def test_remove_crit(self):
16571695
store.remove('wp2', where=[crit1])
16581696
result = store.select('wp2')
16591697
expected = wp.reindex(major_axis=wp.major_axis - date1)
1660-
tm.assert_panel_equal(result, expected)
1698+
assert_panel_equal(result, expected)
16611699

16621700
date2 = wp.major_axis[5]
16631701
crit2 = Term('major_axis', date2)
16641702
store.remove('wp2', where=[crit2])
16651703
result = store['wp2']
16661704
expected = wp.reindex(
16671705
major_axis=wp.major_axis - date1 - Index([date2]))
1668-
tm.assert_panel_equal(result, expected)
1706+
assert_panel_equal(result, expected)
16691707

16701708
date3 = [wp.major_axis[7], wp.major_axis[9]]
16711709
crit3 = Term('major_axis', date3)
16721710
store.remove('wp2', where=[crit3])
16731711
result = store['wp2']
16741712
expected = wp.reindex(
16751713
major_axis=wp.major_axis - date1 - Index([date2]) - Index(date3))
1676-
tm.assert_panel_equal(result, expected)
1714+
assert_panel_equal(result, expected)
16771715

16781716
# corners
16791717
store.put('wp4', wp, table=True)
16801718
n = store.remove(
16811719
'wp4', where=[Term('major_axis', '>', wp.major_axis[-1])])
16821720
result = store.select('wp4')
1683-
tm.assert_panel_equal(result, wp)
1721+
assert_panel_equal(result, wp)
16841722

16851723
def test_terms(self):
16861724

@@ -1710,15 +1748,15 @@ def test_terms(self):
17101748
result = store.select('wp', [Term(
17111749
'major_axis<20000108'), Term('minor_axis', '=', ['A', 'B'])])
17121750
expected = wp.truncate(after='20000108').reindex(minor=['A', 'B'])
1713-
tm.assert_panel_equal(result, expected)
1751+
assert_panel_equal(result, expected)
17141752

17151753
# p4d
17161754
result = store.select('p4d', [Term('major_axis<20000108'),
17171755
Term('minor_axis', '=', ['A', 'B']),
17181756
Term('items', '=', ['ItemA', 'ItemB'])])
17191757
expected = p4d.truncate(after='20000108').reindex(
17201758
minor=['A', 'B'], items=['ItemA', 'ItemB'])
1721-
tm.assert_panel4d_equal(result, expected)
1759+
assert_panel4d_equal(result, expected)
17221760

17231761
# valid terms
17241762
terms = [
@@ -1805,15 +1843,15 @@ def test_sparse_panel(self):
18051843
p = Panel(dict((i, tm.makeDataFrame().ix[:2, :2]) for i in items))
18061844
sp = p.to_sparse()
18071845

1808-
self._check_double_roundtrip(sp, tm.assert_panel_equal,
1846+
self._check_double_roundtrip(sp, assert_panel_equal,
18091847
check_panel_type=True)
18101848

18111849
sp2 = p.to_sparse(kind='integer')
1812-
self._check_double_roundtrip(sp2, tm.assert_panel_equal,
1850+
self._check_double_roundtrip(sp2, assert_panel_equal,
18131851
check_panel_type=True)
18141852

18151853
sp3 = p.to_sparse(fill_value=0)
1816-
self._check_double_roundtrip(sp3, tm.assert_panel_equal,
1854+
self._check_double_roundtrip(sp3, assert_panel_equal,
18171855
check_panel_type=True)
18181856

18191857
def test_float_index(self):
@@ -2034,12 +2072,12 @@ def _make_one():
20342072
def test_wide(self):
20352073

20362074
wp = tm.makePanel()
2037-
self._check_roundtrip(wp, tm.assert_panel_equal)
2075+
self._check_roundtrip(wp, assert_panel_equal)
20382076

20392077
def test_wide_table(self):
20402078

20412079
wp = tm.makePanel()
2042-
self._check_roundtrip_table(wp, tm.assert_panel_equal)
2080+
self._check_roundtrip_table(wp, assert_panel_equal)
20432081

20442082
def test_wide_table_dups(self):
20452083
wp = tm.makePanel()
@@ -2050,11 +2088,11 @@ def test_wide_table_dups(self):
20502088
with tm.assert_produces_warning(expected_warning=DuplicateWarning):
20512089
recons = store['panel']
20522090

2053-
tm.assert_panel_equal(recons, wp)
2091+
assert_panel_equal(recons, wp)
20542092

20552093
def test_long(self):
20562094
def _check(left, right):
2057-
tm.assert_panel_equal(left.to_panel(), right.to_panel())
2095+
assert_panel_equal(left.to_panel(), right.to_panel())
20582096

20592097
wp = tm.makePanel()
20602098
self._check_roundtrip(wp.to_frame(), _check)
@@ -2129,7 +2167,7 @@ def test_select(self):
21292167
items = ['Item%03d' % i for i in range(80)]
21302168
result = store.select('wp', Term('items', items))
21312169
expected = wp.reindex(items=items)
2132-
tm.assert_panel_equal(expected, result)
2170+
assert_panel_equal(expected, result)
21332171

21342172
# selectin non-table with a where
21352173
# self.assertRaises(ValueError, store.select,
@@ -2414,12 +2452,12 @@ def test_panel_select(self):
24142452

24152453
result = store.select('wp', [crit1, crit2])
24162454
expected = wp.truncate(before=date).reindex(minor=['A', 'D'])
2417-
tm.assert_panel_equal(result, expected)
2455+
assert_panel_equal(result, expected)
24182456

24192457
result = store.select(
24202458
'wp', ['major_axis>=20000124', ('minor_axis', '=', ['A', 'B'])])
24212459
expected = wp.truncate(before='20000124').reindex(minor=['A', 'B'])
2422-
tm.assert_panel_equal(result, expected)
2460+
assert_panel_equal(result, expected)
24232461

24242462
def test_frame_select(self):
24252463

0 commit comments

Comments
 (0)