Skip to content

Commit 8e7b03e

Browse files
authored
Merge branch 'master' into thead-improvement
2 parents 6ae2860 + f65a641 commit 8e7b03e

File tree

9 files changed

+117
-101
lines changed

9 files changed

+117
-101
lines changed

doc/source/io.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,8 +2833,8 @@ Style and Formatting
28332833
The look and feel of Excel worksheets created from pandas can be modified using the following parameters on the ``DataFrame``'s ``to_excel`` method.
28342834

28352835
- ``float_format`` : Format string for floating point numbers (default None)
2836-
- ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will
2837-
freeze the first row and first column (default None)
2836+
- ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will freeze the first row and first column (default None)
2837+
28382838

28392839

28402840
.. _io.clipboard:

doc/source/whatsnew/v0.20.0.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Other enhancements
155155
- ``Series/DataFrame.squeeze()`` have gained the ``axis`` parameter. (:issue:`15339`)
156156
- ``DataFrame.to_excel()`` has a new ``freeze_panes`` parameter to turn on Freeze Panes when exporting to Excel (:issue:`15160`)
157157
- ``pd.read_html()`` parses multiple header rows, creating a multiindex header. (:issue:`13434`).
158+
- HTML table output skips ``colspan`` or ``rowspan`` attribute if equal to 1. (:issue:`15403`)
158159

159160
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
160161

@@ -523,7 +524,7 @@ Bug Fixes
523524
- Bug in ``.groupby(...).rolling(...)`` when ``on`` is specified and using a ``DatetimeIndex`` (:issue:`15130`)
524525

525526

526-
527+
- Bug in ``to_sql`` when writing a DataFrame with numeric index names (:issue:`15404`).
527528
- Bug in ``Series.iloc`` where a ``Categorical`` object for list-like indexes input was returned, where a ``Series`` was expected. (:issue:`14580`)
528529

529530

pandas/core/generic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1073,7 +1073,7 @@ def __setstate__(self, state):
10731073
Representation for infinity (there is no native representation for
10741074
infinity in Excel)
10751075
freeze_panes : tuple of integer (length 2), default None
1076-
Specifies the bottommost row and rightmost column that
1076+
Specifies the one-based bottommost row and rightmost column that
10771077
is to be frozen
10781078
10791079
.. versionadded:: 0.20.0

pandas/formats/style.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,23 @@ def format_attr(pair):
251251
"class": " ".join(cs),
252252
"is_visible": True})
253253

254-
for c in range(len(clabels[0])):
254+
for c, value in enumerate(clabels[r]):
255255
cs = [COL_HEADING_CLASS, "level%s" % r, "col%s" % c]
256256
cs.extend(cell_context.get(
257257
"col_headings", {}).get(r, {}).get(c, []))
258-
value = clabels[r][c]
259-
row_es.append({"type": "th",
260-
"value": value,
261-
"display_value": value,
262-
"class": " ".join(cs),
263-
"is_visible": _is_visible(c, r, col_lengths),
264-
"attributes": [
265-
format_attr({"key": "colspan",
266-
"value": col_lengths.get(
267-
(r, c), 1)})
268-
]})
258+
es = {
259+
"type": "th",
260+
"value": value,
261+
"display_value": value,
262+
"class": " ".join(cs),
263+
"is_visible": _is_visible(c, r, col_lengths),
264+
}
265+
colspan = col_lengths.get((r, c), 0)
266+
if colspan > 1:
267+
es["attributes"] = [
268+
format_attr({"key": "colspan", "value": colspan})
269+
]
270+
row_es.append(es)
269271
head.append(row_es)
270272

271273
if self.data.index.names and not all(x is None
@@ -289,19 +291,22 @@ def format_attr(pair):
289291

290292
body = []
291293
for r, idx in enumerate(self.data.index):
292-
# cs.extend(
293-
# cell_context.get("row_headings", {}).get(r, {}).get(c, []))
294-
row_es = [{"type": "th",
295-
"is_visible": _is_visible(r, c, idx_lengths),
296-
"attributes": [
297-
format_attr({"key": "rowspan",
298-
"value": idx_lengths.get((c, r), 1)})
299-
],
300-
"value": rlabels[r][c],
301-
"class": " ".join([ROW_HEADING_CLASS, "level%s" % c,
302-
"row%s" % r]),
303-
"display_value": rlabels[r][c]}
304-
for c in range(len(rlabels[r]))]
294+
row_es = []
295+
for c, value in enumerate(rlabels[r]):
296+
es = {
297+
"type": "th",
298+
"is_visible": _is_visible(r, c, idx_lengths),
299+
"value": value,
300+
"display_value": value,
301+
"class": " ".join([ROW_HEADING_CLASS, "level%s" % c,
302+
"row%s" % r]),
303+
}
304+
rowspan = idx_lengths.get((c, r), 0)
305+
if rowspan > 1:
306+
es["attributes"] = [
307+
format_attr({"key": "rowspan", "value": rowspan})
308+
]
309+
row_es.append(es)
305310

306311
for c, col in enumerate(self.data.columns):
307312
cs = [DATA_CLASS, "row%s" % r, "col%s" % c]

pandas/io/sql.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,8 @@ def _get_column_names_and_types(self, dtype_mapper):
750750
for i, idx_label in enumerate(self.index):
751751
idx_type = dtype_mapper(
752752
self.frame.index.get_level_values(i))
753-
column_names_and_types.append((idx_label, idx_type, True))
753+
column_names_and_types.append((text_type(idx_label),
754+
idx_type, True))
754755

755756
column_names_and_types += [
756757
(text_type(self.frame.columns[i]),
@@ -1220,7 +1221,7 @@ def _create_sql_schema(self, frame, table_name, keys=None, dtype=None):
12201221

12211222
def _get_unicode_name(name):
12221223
try:
1223-
uname = name.encode("utf-8", "strict").decode("utf-8")
1224+
uname = text_type(name).encode("utf-8", "strict").decode("utf-8")
12241225
except UnicodeError:
12251226
raise ValueError("Cannot convert identifier to UTF-8: '%s'" % name)
12261227
return uname

pandas/tests/formats/test_style.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,18 @@ def test_empty_index_name_doesnt_display(self):
141141
'type': 'th',
142142
'value': 'A',
143143
'is_visible': True,
144-
'attributes': ["colspan=1"],
145144
},
146145
{'class': 'col_heading level0 col1',
147146
'display_value': 'B',
148147
'type': 'th',
149148
'value': 'B',
150149
'is_visible': True,
151-
'attributes': ["colspan=1"],
152150
},
153151
{'class': 'col_heading level0 col2',
154152
'display_value': 'C',
155153
'type': 'th',
156154
'value': 'C',
157155
'is_visible': True,
158-
'attributes': ["colspan=1"],
159156
}]]
160157

161158
self.assertEqual(result['head'], expected)
@@ -168,11 +165,9 @@ def test_index_name(self):
168165
expected = [[{'class': 'blank level0', 'type': 'th', 'value': '',
169166
'display_value': '', 'is_visible': True},
170167
{'class': 'col_heading level0 col0', 'type': 'th',
171-
'value': 'B', 'display_value': 'B',
172-
'is_visible': True, 'attributes': ['colspan=1']},
168+
'value': 'B', 'display_value': 'B', 'is_visible': True},
173169
{'class': 'col_heading level0 col1', 'type': 'th',
174-
'value': 'C', 'display_value': 'C',
175-
'is_visible': True, 'attributes': ['colspan=1']}],
170+
'value': 'C', 'display_value': 'C', 'is_visible': True}],
176171
[{'class': 'index_name level0', 'type': 'th',
177172
'value': 'A'},
178173
{'class': 'blank', 'type': 'th', 'value': ''},
@@ -191,9 +186,7 @@ def test_multiindex_name(self):
191186
{'class': 'blank level0', 'type': 'th', 'value': '',
192187
'display_value': '', 'is_visible': True},
193188
{'class': 'col_heading level0 col0', 'type': 'th',
194-
'value': 'C', 'display_value': 'C',
195-
'is_visible': True, 'attributes': ['colspan=1'],
196-
}],
189+
'value': 'C', 'display_value': 'C', 'is_visible': True}],
197190
[{'class': 'index_name level0', 'type': 'th',
198191
'value': 'A'},
199192
{'class': 'index_name level1', 'type': 'th',
@@ -618,16 +611,14 @@ def test_mi_sparse(self):
618611
body_1 = result['body'][0][1]
619612
expected_1 = {
620613
"value": 0, "display_value": 0, "is_visible": True,
621-
"type": "th", "attributes": ["rowspan=1"],
622-
"class": "row_heading level1 row0",
614+
"type": "th", "class": "row_heading level1 row0",
623615
}
624616
tm.assert_dict_equal(body_1, expected_1)
625617

626618
body_10 = result['body'][1][0]
627619
expected_10 = {
628620
"value": 'a', "display_value": 'a', "is_visible": False,
629-
"type": "th", "attributes": ["rowspan=1"],
630-
"class": "row_heading level0 row1",
621+
"type": "th", "class": "row_heading level0 row1",
631622
}
632623
tm.assert_dict_equal(body_10, expected_10)
633624

@@ -637,9 +628,8 @@ def test_mi_sparse(self):
637628
'is_visible': True, "display_value": ''},
638629
{'type': 'th', 'class': 'blank level0', 'value': '',
639630
'is_visible': True, 'display_value': ''},
640-
{'attributes': ['colspan=1'], 'class': 'col_heading level0 col0',
641-
'is_visible': True, 'type': 'th', 'value': 'A',
642-
'display_value': 'A'}]
631+
{'type': 'th', 'class': 'col_heading level0 col0', 'value': 'A',
632+
'is_visible': True, 'display_value': 'A'}]
643633
self.assertEqual(head, expected)
644634

645635
def test_mi_sparse_disabled(self):
@@ -650,7 +640,7 @@ def test_mi_sparse_disabled(self):
650640
result = df.style._translate()
651641
body = result['body']
652642
for row in body:
653-
self.assertEqual(row[0]['attributes'], ['rowspan=1'])
643+
assert 'attributes' not in row[0]
654644

655645
def test_mi_sparse_index_names(self):
656646
df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays(
@@ -686,28 +676,24 @@ def test_mi_sparse_column_names(self):
686676
'type': 'th', 'is_visible': True},
687677
{'class': 'index_name level1', 'value': 'col_1',
688678
'display_value': 'col_1', 'is_visible': True, 'type': 'th'},
689-
{'attributes': ['colspan=1'],
690-
'class': 'col_heading level1 col0',
679+
{'class': 'col_heading level1 col0',
691680
'display_value': 1,
692681
'is_visible': True,
693682
'type': 'th',
694683
'value': 1},
695-
{'attributes': ['colspan=1'],
696-
'class': 'col_heading level1 col1',
684+
{'class': 'col_heading level1 col1',
697685
'display_value': 0,
698686
'is_visible': True,
699687
'type': 'th',
700688
'value': 0},
701689

702-
{'attributes': ['colspan=1'],
703-
'class': 'col_heading level1 col2',
690+
{'class': 'col_heading level1 col2',
704691
'display_value': 1,
705692
'is_visible': True,
706693
'type': 'th',
707694
'value': 1},
708695

709-
{'attributes': ['colspan=1'],
710-
'class': 'col_heading level1 col3',
696+
{'class': 'col_heading level1 col3',
711697
'display_value': 0,
712698
'is_visible': True,
713699
'type': 'th',

pandas/tests/io/test_packers.py

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@
4141
_ZLIB_INSTALLED = True
4242

4343

44+
@pytest.fixture(scope='module')
45+
def current_packers_data():
46+
# our current version packers data
47+
from pandas.tests.io.generate_legacy_storage_files import (
48+
create_msgpack_data)
49+
return create_msgpack_data()
50+
51+
52+
@pytest.fixture(scope='module')
53+
def all_packers_data():
54+
# our all of our current version packers data
55+
from pandas.tests.io.generate_legacy_storage_files import (
56+
create_data)
57+
return create_data()
58+
59+
4460
def check_arbitrary(a, b):
4561

4662
if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
@@ -778,7 +794,16 @@ def test_default_encoding(self):
778794
assert_frame_equal(result, frame)
779795

780796

781-
class TestMsgpack():
797+
def legacy_packers_versions():
798+
# yield the packers versions
799+
path = tm.get_data_path('legacy_msgpack')
800+
for v in os.listdir(path):
801+
p = os.path.join(path, v)
802+
if os.path.isdir(p):
803+
yield v
804+
805+
806+
class TestMsgpack(object):
782807
"""
783808
How to add msgpack tests:
784809
@@ -788,48 +813,38 @@ class TestMsgpack():
788813
$ python generate_legacy_storage_files.py <output_dir> msgpack
789814
790815
3. Move the created pickle to "data/legacy_msgpack/<version>" directory.
791-
792-
NOTE: TestMsgpack can't be a subclass of tm.Testcase to use test generator.
793-
http://stackoverflow.com/questions/6689537/nose-test-generators-inside-class
794816
"""
795817

796-
@classmethod
797-
def setup_class(cls):
798-
from pandas.tests.io.generate_legacy_storage_files import (
799-
create_msgpack_data, create_data)
800-
cls.data = create_msgpack_data()
801-
cls.all_data = create_data()
802-
cls.path = u('__%s__.msgpack' % tm.rands(10))
803-
cls.minimum_structure = {'series': ['float', 'int', 'mixed',
804-
'ts', 'mi', 'dup'],
805-
'frame': ['float', 'int', 'mixed', 'mi'],
806-
'panel': ['float'],
807-
'index': ['int', 'date', 'period'],
808-
'mi': ['reg2']}
809-
810-
def check_min_structure(self, data):
818+
minimum_structure = {'series': ['float', 'int', 'mixed',
819+
'ts', 'mi', 'dup'],
820+
'frame': ['float', 'int', 'mixed', 'mi'],
821+
'panel': ['float'],
822+
'index': ['int', 'date', 'period'],
823+
'mi': ['reg2']}
824+
825+
def check_min_structure(self, data, version):
811826
for typ, v in self.minimum_structure.items():
812827
assert typ in data, '"{0}" not found in unpacked data'.format(typ)
813828
for kind in v:
814829
msg = '"{0}" not found in data["{1}"]'.format(kind, typ)
815830
assert kind in data[typ], msg
816831

817-
def compare(self, vf, version):
832+
def compare(self, current_data, all_data, vf, version):
818833
# GH12277 encoding default used to be latin-1, now utf-8
819834
if LooseVersion(version) < '0.18.0':
820835
data = read_msgpack(vf, encoding='latin-1')
821836
else:
822837
data = read_msgpack(vf)
823-
self.check_min_structure(data)
838+
self.check_min_structure(data, version)
824839
for typ, dv in data.items():
825-
assert typ in self.all_data, ('unpacked data contains '
826-
'extra key "{0}"'
827-
.format(typ))
840+
assert typ in all_data, ('unpacked data contains '
841+
'extra key "{0}"'
842+
.format(typ))
828843
for dt, result in dv.items():
829-
assert dt in self.all_data[typ], ('data["{0}"] contains extra '
830-
'key "{1}"'.format(typ, dt))
844+
assert dt in current_data[typ], ('data["{0}"] contains extra '
845+
'key "{1}"'.format(typ, dt))
831846
try:
832-
expected = self.data[typ][dt]
847+
expected = current_data[typ][dt]
833848
except KeyError:
834849
continue
835850

@@ -862,9 +877,11 @@ def compare_frame_dt_mixed_tzs(self, result, expected, typ, version):
862877
else:
863878
tm.assert_frame_equal(result, expected)
864879

865-
def read_msgpacks(self, version):
880+
@pytest.mark.parametrize('version', legacy_packers_versions())
881+
def test_msgpacks_legacy(self, current_packers_data, all_packers_data,
882+
version):
866883

867-
pth = tm.get_data_path('legacy_msgpack/{0}'.format(str(version)))
884+
pth = tm.get_data_path('legacy_msgpack/{0}'.format(version))
868885
n = 0
869886
for f in os.listdir(pth):
870887
# GH12142 0.17 files packed in P2 can't be read in P3
@@ -873,19 +890,10 @@ def read_msgpacks(self, version):
873890
continue
874891
vf = os.path.join(pth, f)
875892
try:
876-
self.compare(vf, version)
893+
self.compare(current_packers_data, all_packers_data,
894+
vf, version)
877895
except ImportError:
878896
# blosc not installed
879897
continue
880898
n += 1
881899
assert n > 0, 'Msgpack files are not tested'
882-
883-
def test_msgpack(self):
884-
msgpack_path = tm.get_data_path('legacy_msgpack')
885-
n = 0
886-
for v in os.listdir(msgpack_path):
887-
pth = os.path.join(msgpack_path, v)
888-
if os.path.isdir(pth):
889-
yield self.read_msgpacks, v
890-
n += 1
891-
assert n > 0, 'Msgpack files are not tested'

0 commit comments

Comments
 (0)