From a7df84123f4a5bc346377563da8a8e98e82b287d Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Thu, 17 Mar 2016 08:51:41 -0400 Subject: [PATCH 01/19] Modest performance, address #12647 --- pandas/io/sas/sas7bdat.py | 18 ++++++++++------- pandas/io/sas/saslib.pyx | 41 ++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index e068c51df585d..a03ced57babe2 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -327,12 +327,12 @@ def _get_properties(self): _os_version_number_length) self.os_version = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes( - _os_name_offset, _os_name_length).rstrip(b'\x00 ') + buf = self._read_bytes(_os_name_offset, _os_name_length) + buf = buf.rstrip(b'\x00 ') if len(buf) > 0: - self.os_name = buf.rstrip(b'\x00 ').decode() + self.os_name = buf.decode() else: - buf = self._path_or_buf.read(_os_maker_offset, _os_maker_length) + buf = self._read_bytes(_os_maker_offset, _os_maker_length) self.os_name = buf.rstrip(b'\x00 ').decode() # Read a single float of the given width (4 or 8). @@ -592,6 +592,10 @@ def _process_columnattributes_subheader(self, offset, length): length - 2 * int_len - 12) // (int_len + 8) self.column_types = np.empty( column_attributes_vectors_count, dtype=np.dtype('S1')) + self._column_data_lengths = np.empty( + column_attributes_vectors_count, dtype=np.int64) + self._column_data_offsets = np.empty( + column_attributes_vectors_count, dtype=np.int64) for i in range(column_attributes_vectors_count): col_data_offset = (offset + int_len + _column_data_offset_offset + i * (int_len + 8)) @@ -600,11 +604,11 @@ def _process_columnattributes_subheader(self, offset, length): col_types = (offset + 2 * int_len + _column_type_offset + i * (int_len + 8)) - self._column_data_offsets.append( - self._read_int(col_data_offset, int_len)) + x = self._read_int(col_data_offset, int_len) + self._column_data_offsets[i] = x x = self._read_int(col_data_len, _column_data_length_length) - self._column_data_lengths.append(x) + self._column_data_lengths[i] = x x = self._read_int(col_types, _column_type_length) if x == 1: diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index a963bf4fe25d3..bfaff79dcb30e 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -1,6 +1,6 @@ import numpy as np cimport numpy as np -from numpy cimport uint8_t, uint16_t +from numpy cimport uint8_t, uint16_t, int8_t # rle_decompress decompresses data using a Run Length Encoding # algorithm. It is partially documented here: @@ -191,43 +191,44 @@ def _rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): return np.asarray(outbuff).tostring() -def process_byte_array_with_data(parser, int offset, int length, np.ndarray[uint8_t, ndim=2] byte_chunk, - np.ndarray[dtype=object, ndim=2] string_chunk): +def process_byte_array_with_data(parser, int offset, int length, uint8_t[:, ::1] byte_chunk, + object[:, ::1] string_chunk): cdef int s cdef int j + cdef int k cdef int m cdef int start - cdef int end - cdef bytes source - cdef bytes temp cdef int jb cdef int js + cdef int lngt + + cdef long[:] lengths = parser._column_data_lengths + cdef long[:] offsets = parser._column_data_offsets + cdef char[:] column_types = parser.column_types + source = parser._cached_page[offset:offset+length] if (parser.compression != "") and (length < parser.row_length): - source = parser._decompress(parser.row_length, parser._cached_page[offset:offset + length]) - else: - source = parser._cached_page[offset:offset + length] + source = parser._decompress(parser.row_length, source) s = 8 * parser._current_row_in_chunk_index js = 0 jb = 0 for j in range(parser.column_count): - length = parser._column_data_lengths[j] - if length == 0: + lngt = lengths[j] + if lngt == 0: break - start = parser._column_data_offsets[j] - end = start + length - temp = source[start:end] - if parser.column_types[j] == b'd': - m = 8 - length + start = offsets[j] + if column_types[j] == b'd': if parser.byte_order == "<": - byte_chunk[jb, s+m:s+8] = np.frombuffer(temp, dtype=np.uint8) + m = s + 8 - lngt else: - byte_chunk[jb, s:s+length] = np.frombuffer(temp, dtype=np.uint8) + m = s + for k in range(lngt): + byte_chunk[jb, m + k] = source[start + k] jb += 1 - elif parser.column_types[j] == b's': - string_chunk[js, parser._current_row_in_chunk_index] = bytes(temp) + elif column_types[j] == b's': + string_chunk[js, parser._current_row_in_chunk_index] = bytes(source[start:start+lngt]) js += 1 else: raise ValueError("unknown column type: %s" % parser.columns[j].ctype) From 7d91d51464845fde8f123489fc2c9cec06605638 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Fri, 18 Mar 2016 23:38:40 -0400 Subject: [PATCH 02/19] Add test data set from raderaj --- pandas/io/tests/sas/data/productsales.csv | 1441 +++++++++++++++++ .../io/tests/sas/data/productsales.sas7bdat | Bin 0 -> 148480 bytes pandas/io/tests/sas/test_sas7bdat.py | 11 + 3 files changed, 1452 insertions(+) create mode 100644 pandas/io/tests/sas/data/productsales.csv create mode 100644 pandas/io/tests/sas/data/productsales.sas7bdat diff --git a/pandas/io/tests/sas/data/productsales.csv b/pandas/io/tests/sas/data/productsales.csv new file mode 100644 index 0000000000000..fea9b68912297 --- /dev/null +++ b/pandas/io/tests/sas/data/productsales.csv @@ -0,0 +1,1441 @@ +ACTUAL,PREDICT,COUNTRY,REGION,DIVISION,PRODTYPE,PRODUCT,QUARTER,YEAR,MONTH +925,850,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +999,297,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +608,846,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +642,533,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +656,646,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +948,486,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +612,717,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +114,564,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +685,230,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +657,494,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +608,903,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +353,266,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +107,190,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +354,139,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +101,217,CANADA,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +553,560,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +877,148,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +431,762,CANADA,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +511,457,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +157,532,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +520,629,CANADA,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +114,491,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +277,0,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +561,979,CANADA,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +220,585,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1993,12054 +444,267,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1993,12085 +178,487,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1993,12113 +756,764,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1993,12144 +329,312,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1993,12174 +910,531,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1993,12205 +530,536,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1993,12235 +101,773,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1993,12266 +515,143,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1993,12297 +730,126,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1993,12327 +993,862,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1993,12358 +954,754,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1993,12388 +267,410,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1994,12419 +347,701,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1994,12450 +991,204,CANADA,EAST,EDUCATION,FURNITURE,BED,1,1994,12478 +923,509,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1994,12509 +437,378,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1994,12539 +737,507,CANADA,EAST,EDUCATION,FURNITURE,BED,2,1994,12570 +104,49,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1994,12600 +840,876,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1994,12631 +704,66,CANADA,EAST,EDUCATION,FURNITURE,BED,3,1994,12662 +889,819,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1994,12692 +107,351,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1994,12723 +571,201,CANADA,EAST,EDUCATION,FURNITURE,BED,4,1994,12753 +688,209,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1993,12054 +544,51,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1993,12085 +954,135,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1993,12113 +445,47,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1993,12144 +829,379,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1993,12174 +464,758,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1993,12205 +968,475,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1993,12235 +842,343,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1993,12266 +721,507,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1993,12297 +966,269,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1993,12327 +332,699,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1993,12358 +328,824,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1993,12388 +355,497,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1994,12419 +506,44,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1994,12450 +585,522,CANADA,EAST,EDUCATION,OFFICE,TABLE,1,1994,12478 +634,378,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1994,12509 +662,689,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1994,12539 +783,90,CANADA,EAST,EDUCATION,OFFICE,TABLE,2,1994,12570 +786,720,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1994,12600 +710,343,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1994,12631 +950,457,CANADA,EAST,EDUCATION,OFFICE,TABLE,3,1994,12662 +274,947,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1994,12692 +406,834,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1994,12723 +515,71,CANADA,EAST,EDUCATION,OFFICE,TABLE,4,1994,12753 +35,282,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +995,538,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +670,679,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +406,601,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +825,577,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +467,908,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +709,819,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +522,687,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +688,157,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +956,111,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +129,31,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +687,790,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +877,795,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +845,379,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +425,114,CANADA,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +899,475,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +987,747,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +641,372,CANADA,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +448,415,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +341,955,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +137,356,CANADA,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +235,316,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +482,351,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +678,164,CANADA,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +240,386,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1993,12054 +605,113,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1993,12085 +274,68,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1993,12113 +422,885,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1993,12144 +763,575,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1993,12174 +561,743,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1993,12205 +339,816,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1993,12235 +877,203,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1993,12266 +192,581,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1993,12297 +604,815,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1993,12327 +55,333,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1993,12358 +87,40,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1993,12388 +942,672,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1994,12419 +912,23,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1994,12450 +768,948,CANADA,EAST,EDUCATION,OFFICE,DESK,1,1994,12478 +951,291,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1994,12509 +768,839,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1994,12539 +978,864,CANADA,EAST,EDUCATION,OFFICE,DESK,2,1994,12570 +20,337,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1994,12600 +298,95,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1994,12631 +193,535,CANADA,EAST,EDUCATION,OFFICE,DESK,3,1994,12662 +336,191,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1994,12692 +617,412,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1994,12723 +709,711,CANADA,EAST,EDUCATION,OFFICE,DESK,4,1994,12753 +5,425,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +164,215,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +422,948,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +424,544,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +854,764,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +168,446,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +8,957,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +748,967,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +682,11,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +300,110,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +672,263,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +894,215,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +944,965,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +403,423,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +596,753,CANADA,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +481,770,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +503,263,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +126,79,CANADA,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +721,441,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +271,858,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +721,667,CANADA,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +157,193,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +991,394,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +499,680,CANADA,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +284,414,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1993,12054 +705,770,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1993,12085 +737,679,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1993,12113 +745,7,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1993,12144 +633,713,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1993,12174 +983,851,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1993,12205 +591,944,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1993,12235 +42,130,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1993,12266 +771,485,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1993,12297 +465,23,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1993,12327 +296,193,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1993,12358 +890,7,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1993,12388 +312,919,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1994,12419 +777,768,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1994,12450 +364,854,CANADA,EAST,CONSUMER,FURNITURE,BED,1,1994,12478 +601,411,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1994,12509 +823,736,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1994,12539 +847,10,CANADA,EAST,CONSUMER,FURNITURE,BED,2,1994,12570 +490,311,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1994,12600 +387,348,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1994,12631 +688,458,CANADA,EAST,CONSUMER,FURNITURE,BED,3,1994,12662 +650,195,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1994,12692 +447,658,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1994,12723 +91,704,CANADA,EAST,CONSUMER,FURNITURE,BED,4,1994,12753 +197,807,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1993,12054 +51,861,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1993,12085 +570,873,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1993,12113 +423,933,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1993,12144 +524,355,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1993,12174 +416,794,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1993,12205 +789,645,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1993,12235 +551,700,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1993,12266 +400,831,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1993,12297 +361,800,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1993,12327 +189,830,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1993,12358 +554,828,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1993,12388 +585,12,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1994,12419 +281,501,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1994,12450 +629,914,CANADA,EAST,CONSUMER,OFFICE,TABLE,1,1994,12478 +43,685,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1994,12509 +533,755,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1994,12539 +882,708,CANADA,EAST,CONSUMER,OFFICE,TABLE,2,1994,12570 +790,595,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1994,12600 +600,32,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1994,12631 +148,49,CANADA,EAST,CONSUMER,OFFICE,TABLE,3,1994,12662 +237,727,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1994,12692 +488,239,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1994,12723 +457,273,CANADA,EAST,CONSUMER,OFFICE,TABLE,4,1994,12753 +401,986,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +181,544,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +995,182,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +120,197,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +119,435,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +319,974,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +333,524,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +923,688,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +634,750,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +493,155,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +461,860,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +304,102,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +641,425,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +992,224,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +202,408,CANADA,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +770,524,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +202,816,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +14,515,CANADA,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +134,793,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +977,460,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +174,732,CANADA,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +429,435,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +514,38,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +784,616,CANADA,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +973,225,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1993,12054 +511,402,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1993,12085 +30,697,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1993,12113 +895,567,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1993,12144 +557,231,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1993,12174 +282,372,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1993,12205 +909,15,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1993,12235 +276,866,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1993,12266 +234,452,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1993,12297 +479,663,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1993,12327 +782,982,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1993,12358 +755,813,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1993,12388 +689,523,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1994,12419 +496,871,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1994,12450 +24,511,CANADA,EAST,CONSUMER,OFFICE,DESK,1,1994,12478 +379,819,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1994,12509 +441,525,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1994,12539 +49,13,CANADA,EAST,CONSUMER,OFFICE,DESK,2,1994,12570 +243,694,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1994,12600 +295,782,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1994,12631 +395,839,CANADA,EAST,CONSUMER,OFFICE,DESK,3,1994,12662 +929,461,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1994,12692 +997,303,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1994,12723 +889,421,CANADA,EAST,CONSUMER,OFFICE,DESK,4,1994,12753 +72,421,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +926,433,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +850,394,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +826,338,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +651,764,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +854,216,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +899,96,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +309,550,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +943,636,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +138,427,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +99,652,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +270,478,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +862,18,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +574,40,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +359,453,CANADA,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +958,987,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +791,26,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +284,101,CANADA,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +190,969,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +527,492,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +112,263,CANADA,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +271,593,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +643,923,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +554,146,CANADA,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +211,305,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1993,12054 +368,318,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1993,12085 +778,417,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1993,12113 +808,623,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1993,12144 +46,761,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1993,12174 +466,272,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1993,12205 +18,988,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1993,12235 +87,821,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1993,12266 +765,962,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1993,12297 +62,615,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1993,12327 +13,523,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1993,12358 +775,806,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1993,12388 +636,586,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1994,12419 +458,520,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1994,12450 +206,908,CANADA,WEST,EDUCATION,FURNITURE,BED,1,1994,12478 +310,30,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1994,12509 +813,247,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1994,12539 +22,647,CANADA,WEST,EDUCATION,FURNITURE,BED,2,1994,12570 +742,55,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1994,12600 +394,154,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1994,12631 +957,344,CANADA,WEST,EDUCATION,FURNITURE,BED,3,1994,12662 +205,95,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1994,12692 +198,665,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1994,12723 +638,145,CANADA,WEST,EDUCATION,FURNITURE,BED,4,1994,12753 +155,925,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1993,12054 +688,395,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1993,12085 +730,749,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1993,12113 +208,279,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1993,12144 +525,288,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1993,12174 +483,509,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1993,12205 +748,255,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1993,12235 +6,214,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1993,12266 +168,473,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1993,12297 +301,702,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1993,12327 +9,814,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1993,12358 +778,231,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1993,12388 +799,422,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1994,12419 +309,572,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1994,12450 +433,363,CANADA,WEST,EDUCATION,OFFICE,TABLE,1,1994,12478 +969,919,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1994,12509 +181,355,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1994,12539 +787,992,CANADA,WEST,EDUCATION,OFFICE,TABLE,2,1994,12570 +971,147,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1994,12600 +440,183,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1994,12631 +209,375,CANADA,WEST,EDUCATION,OFFICE,TABLE,3,1994,12662 +537,77,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1994,12692 +364,308,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1994,12723 +377,660,CANADA,WEST,EDUCATION,OFFICE,TABLE,4,1994,12753 +251,555,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +607,455,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +127,888,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +513,652,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +146,799,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +917,249,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +776,539,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +330,198,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +981,340,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +862,152,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +612,347,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +607,565,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +786,855,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +160,87,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +199,69,CANADA,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +972,807,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +870,565,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +494,798,CANADA,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +975,714,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +760,17,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +180,797,CANADA,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +256,422,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +422,621,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +859,661,CANADA,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +586,363,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1993,12054 +441,910,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1993,12085 +597,998,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1993,12113 +717,95,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1993,12144 +713,731,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1993,12174 +591,718,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1993,12205 +492,467,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1993,12235 +170,126,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1993,12266 +684,127,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1993,12297 +981,746,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1993,12327 +966,878,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1993,12358 +439,27,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1993,12388 +151,569,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1994,12419 +602,812,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1994,12450 +187,603,CANADA,WEST,EDUCATION,OFFICE,DESK,1,1994,12478 +415,506,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1994,12509 +61,185,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1994,12539 +839,692,CANADA,WEST,EDUCATION,OFFICE,DESK,2,1994,12570 +596,565,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1994,12600 +751,512,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1994,12631 +460,86,CANADA,WEST,EDUCATION,OFFICE,DESK,3,1994,12662 +922,399,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1994,12692 +153,672,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1994,12723 +928,801,CANADA,WEST,EDUCATION,OFFICE,DESK,4,1994,12753 +951,730,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +394,408,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +615,982,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +653,499,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +180,307,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +649,741,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +921,640,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +11,300,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +696,929,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +795,309,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +550,340,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +320,228,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +845,1000,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +245,21,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +142,583,CANADA,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +717,506,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +3,405,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +790,556,CANADA,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +646,72,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +230,103,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +938,262,CANADA,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +629,102,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +317,841,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +812,159,CANADA,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +141,570,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1993,12054 +64,375,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1993,12085 +207,298,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1993,12113 +435,32,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1993,12144 +96,760,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1993,12174 +252,338,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1993,12205 +956,149,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1993,12235 +633,343,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1993,12266 +190,151,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1993,12297 +227,44,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1993,12327 +24,583,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1993,12358 +420,230,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1993,12388 +910,907,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1994,12419 +709,783,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1994,12450 +810,117,CANADA,WEST,CONSUMER,FURNITURE,BED,1,1994,12478 +723,416,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1994,12509 +911,318,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1994,12539 +230,888,CANADA,WEST,CONSUMER,FURNITURE,BED,2,1994,12570 +448,60,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1994,12600 +945,596,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1994,12631 +508,576,CANADA,WEST,CONSUMER,FURNITURE,BED,3,1994,12662 +262,576,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1994,12692 +441,280,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1994,12723 +15,219,CANADA,WEST,CONSUMER,FURNITURE,BED,4,1994,12753 +795,133,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1993,12054 +301,273,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1993,12085 +304,86,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1993,12113 +49,400,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1993,12144 +576,364,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1993,12174 +669,63,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1993,12205 +325,929,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1993,12235 +272,344,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1993,12266 +80,768,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1993,12297 +46,668,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1993,12327 +223,407,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1993,12358 +774,536,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1993,12388 +784,657,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1994,12419 +92,215,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1994,12450 +67,966,CANADA,WEST,CONSUMER,OFFICE,TABLE,1,1994,12478 +747,674,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1994,12509 +686,574,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1994,12539 +93,266,CANADA,WEST,CONSUMER,OFFICE,TABLE,2,1994,12570 +192,680,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1994,12600 +51,362,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1994,12631 +498,412,CANADA,WEST,CONSUMER,OFFICE,TABLE,3,1994,12662 +546,431,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1994,12692 +485,94,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1994,12723 +925,345,CANADA,WEST,CONSUMER,OFFICE,TABLE,4,1994,12753 +292,445,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +540,632,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +21,855,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +100,36,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +49,250,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +353,427,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +911,367,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +823,245,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +278,893,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +576,490,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +655,88,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +763,964,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +88,62,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +746,506,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +927,680,CANADA,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +297,153,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +291,403,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +838,98,CANADA,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +112,376,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +509,477,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +472,50,CANADA,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +495,592,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +1000,813,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +241,740,CANADA,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +693,873,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1993,12054 +903,459,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1993,12085 +791,224,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1993,12113 +108,562,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1993,12144 +845,199,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1993,12174 +452,275,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1993,12205 +479,355,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1993,12235 +410,947,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1993,12266 +379,454,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1993,12297 +740,450,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1993,12327 +471,575,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1993,12358 +325,6,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1993,12388 +455,847,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1994,12419 +563,338,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1994,12450 +879,517,CANADA,WEST,CONSUMER,OFFICE,DESK,1,1994,12478 +312,630,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1994,12509 +587,381,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1994,12539 +628,864,CANADA,WEST,CONSUMER,OFFICE,DESK,2,1994,12570 +486,416,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1994,12600 +811,852,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1994,12631 +990,815,CANADA,WEST,CONSUMER,OFFICE,DESK,3,1994,12662 +35,23,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1994,12692 +764,527,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1994,12723 +619,693,CANADA,WEST,CONSUMER,OFFICE,DESK,4,1994,12753 +996,977,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +554,549,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +540,951,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +140,390,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +554,204,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +724,78,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +693,613,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +866,745,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +833,56,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +164,887,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +753,651,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +60,691,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +688,767,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +883,709,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +109,417,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +950,326,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +438,599,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +286,818,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +342,13,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +383,185,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +80,140,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +322,717,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +749,852,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +606,125,GERMANY,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +641,325,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1993,12054 +494,648,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1993,12085 +428,365,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1993,12113 +936,120,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1993,12144 +597,347,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1993,12174 +728,638,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1993,12205 +933,732,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1993,12235 +663,465,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1993,12266 +394,262,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1993,12297 +334,947,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1993,12327 +114,694,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1993,12358 +89,482,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1993,12388 +874,600,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1994,12419 +674,94,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1994,12450 +347,323,GERMANY,EAST,EDUCATION,FURNITURE,BED,1,1994,12478 +105,49,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1994,12509 +286,70,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1994,12539 +669,844,GERMANY,EAST,EDUCATION,FURNITURE,BED,2,1994,12570 +786,773,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1994,12600 +104,68,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1994,12631 +770,110,GERMANY,EAST,EDUCATION,FURNITURE,BED,3,1994,12662 +263,42,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1994,12692 +900,171,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1994,12723 +630,644,GERMANY,EAST,EDUCATION,FURNITURE,BED,4,1994,12753 +597,408,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1993,12054 +185,45,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1993,12085 +175,522,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1993,12113 +576,166,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1993,12144 +957,885,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1993,12174 +993,713,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1993,12205 +500,838,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1993,12235 +410,267,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1993,12266 +592,967,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1993,12297 +64,529,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1993,12327 +208,656,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1993,12358 +273,665,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1993,12388 +906,419,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1994,12419 +429,776,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1994,12450 +961,971,GERMANY,EAST,EDUCATION,OFFICE,TABLE,1,1994,12478 +338,248,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1994,12509 +472,486,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1994,12539 +903,674,GERMANY,EAST,EDUCATION,OFFICE,TABLE,2,1994,12570 +299,603,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1994,12600 +948,492,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1994,12631 +931,512,GERMANY,EAST,EDUCATION,OFFICE,TABLE,3,1994,12662 +570,391,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1994,12692 +97,313,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1994,12723 +674,758,GERMANY,EAST,EDUCATION,OFFICE,TABLE,4,1994,12753 +468,304,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +430,846,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +893,912,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +519,810,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +267,122,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +908,102,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +176,161,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +673,450,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +798,215,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +291,765,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +583,557,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +442,739,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +951,811,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +430,780,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +559,645,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +726,365,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +944,597,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +497,126,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +388,655,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +81,604,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +111,280,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +288,115,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +845,205,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +745,672,GERMANY,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +352,339,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1993,12054 +234,70,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1993,12085 +167,528,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1993,12113 +606,220,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1993,12144 +670,691,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1993,12174 +764,197,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1993,12205 +659,239,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1993,12235 +996,50,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1993,12266 +424,135,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1993,12297 +899,972,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1993,12327 +392,475,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1993,12358 +555,868,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1993,12388 +860,451,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1994,12419 +114,565,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1994,12450 +943,116,GERMANY,EAST,EDUCATION,OFFICE,DESK,1,1994,12478 +365,385,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1994,12509 +249,375,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1994,12539 +192,357,GERMANY,EAST,EDUCATION,OFFICE,DESK,2,1994,12570 +328,230,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1994,12600 +311,829,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1994,12631 +576,971,GERMANY,EAST,EDUCATION,OFFICE,DESK,3,1994,12662 +915,280,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1994,12692 +522,853,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1994,12723 +625,953,GERMANY,EAST,EDUCATION,OFFICE,DESK,4,1994,12753 +873,874,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +498,578,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +808,768,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +742,178,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +744,916,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +30,917,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +747,633,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +672,107,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +564,523,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +785,924,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +825,481,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +243,240,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +959,819,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +123,602,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +714,538,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +252,632,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +715,952,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +670,480,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +81,700,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +653,726,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +795,526,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +182,410,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +725,307,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +101,73,GERMANY,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +143,232,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1993,12054 +15,993,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1993,12085 +742,652,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1993,12113 +339,761,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1993,12144 +39,428,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1993,12174 +465,4,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1993,12205 +889,101,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1993,12235 +856,869,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1993,12266 +358,271,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1993,12297 +452,633,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1993,12327 +387,481,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1993,12358 +824,302,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1993,12388 +185,245,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1994,12419 +151,941,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1994,12450 +419,721,GERMANY,EAST,CONSUMER,FURNITURE,BED,1,1994,12478 +643,893,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1994,12509 +63,898,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1994,12539 +202,94,GERMANY,EAST,CONSUMER,FURNITURE,BED,2,1994,12570 +332,962,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1994,12600 +723,71,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1994,12631 +148,108,GERMANY,EAST,CONSUMER,FURNITURE,BED,3,1994,12662 +840,71,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1994,12692 +601,767,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1994,12723 +962,323,GERMANY,EAST,CONSUMER,FURNITURE,BED,4,1994,12753 +166,982,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1993,12054 +531,614,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1993,12085 +963,839,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1993,12113 +994,388,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1993,12144 +978,296,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1993,12174 +72,429,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1993,12205 +33,901,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1993,12235 +428,350,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1993,12266 +413,581,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1993,12297 +737,583,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1993,12327 +85,92,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1993,12358 +916,647,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1993,12388 +785,771,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1994,12419 +302,26,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1994,12450 +1000,598,GERMANY,EAST,CONSUMER,OFFICE,TABLE,1,1994,12478 +458,715,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1994,12509 +896,74,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1994,12539 +615,580,GERMANY,EAST,CONSUMER,OFFICE,TABLE,2,1994,12570 +174,848,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1994,12600 +651,118,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1994,12631 +784,54,GERMANY,EAST,CONSUMER,OFFICE,TABLE,3,1994,12662 +121,929,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1994,12692 +341,393,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1994,12723 +615,820,GERMANY,EAST,CONSUMER,OFFICE,TABLE,4,1994,12753 +697,336,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +215,299,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +197,747,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +205,154,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +256,486,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +377,251,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +577,225,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +686,77,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +332,74,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +534,596,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +485,493,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +594,782,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +413,487,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +13,127,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +483,538,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +820,94,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +745,252,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +79,722,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +36,536,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +950,958,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +74,466,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +458,309,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +609,680,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +429,539,GERMANY,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +956,511,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1993,12054 +205,505,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1993,12085 +629,720,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1993,12113 +277,823,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1993,12144 +266,21,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1993,12174 +872,142,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1993,12205 +435,95,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1993,12235 +988,398,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1993,12266 +953,328,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1993,12297 +556,151,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1993,12327 +211,978,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1993,12358 +389,918,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1993,12388 +351,542,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1994,12419 +14,96,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1994,12450 +181,496,GERMANY,EAST,CONSUMER,OFFICE,DESK,1,1994,12478 +452,77,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1994,12509 +511,236,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1994,12539 +193,913,GERMANY,EAST,CONSUMER,OFFICE,DESK,2,1994,12570 +797,49,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1994,12600 +988,967,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1994,12631 +487,502,GERMANY,EAST,CONSUMER,OFFICE,DESK,3,1994,12662 +941,790,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1994,12692 +577,121,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1994,12723 +456,55,GERMANY,EAST,CONSUMER,OFFICE,DESK,4,1994,12753 +982,739,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +593,683,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +702,610,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +528,248,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +873,530,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +301,889,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +769,245,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +724,473,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +466,938,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +774,150,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +111,772,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +954,201,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +780,945,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +210,177,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +93,378,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +332,83,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +186,803,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +782,398,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +41,215,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +222,194,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +992,287,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +477,410,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +948,50,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +817,204,GERMANY,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +597,239,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1993,12054 +649,637,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1993,12085 +3,938,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1993,12113 +731,788,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1993,12144 +181,399,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1993,12174 +468,576,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1993,12205 +891,187,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1993,12235 +226,703,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1993,12266 +28,455,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1993,12297 +609,244,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1993,12327 +224,868,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1993,12358 +230,353,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1993,12388 +216,101,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1994,12419 +282,924,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1994,12450 +501,144,GERMANY,WEST,EDUCATION,FURNITURE,BED,1,1994,12478 +320,0,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1994,12509 +720,910,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1994,12539 +464,259,GERMANY,WEST,EDUCATION,FURNITURE,BED,2,1994,12570 +363,107,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1994,12600 +49,63,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1994,12631 +223,270,GERMANY,WEST,EDUCATION,FURNITURE,BED,3,1994,12662 +452,554,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1994,12692 +210,154,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1994,12723 +444,205,GERMANY,WEST,EDUCATION,FURNITURE,BED,4,1994,12753 +222,441,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1993,12054 +678,183,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1993,12085 +25,459,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1993,12113 +57,810,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1993,12144 +981,268,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1993,12174 +740,916,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1993,12205 +408,742,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1993,12235 +966,522,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1993,12266 +107,299,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1993,12297 +488,677,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1993,12327 +759,709,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1993,12358 +504,310,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1993,12388 +99,160,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1994,12419 +503,698,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1994,12450 +724,540,GERMANY,WEST,EDUCATION,OFFICE,TABLE,1,1994,12478 +309,901,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1994,12509 +625,34,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1994,12539 +294,536,GERMANY,WEST,EDUCATION,OFFICE,TABLE,2,1994,12570 +890,780,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1994,12600 +501,716,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1994,12631 +34,532,GERMANY,WEST,EDUCATION,OFFICE,TABLE,3,1994,12662 +203,871,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1994,12692 +140,199,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1994,12723 +845,845,GERMANY,WEST,EDUCATION,OFFICE,TABLE,4,1994,12753 +774,591,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +645,378,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +986,942,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +296,686,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +936,720,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +341,546,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +32,845,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +277,667,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +548,627,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +727,142,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +812,655,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +168,556,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +150,459,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +136,89,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +695,726,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +363,38,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +853,60,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +621,369,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +764,381,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +669,465,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +772,981,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +228,758,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +261,31,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +821,237,GERMANY,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +100,285,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1993,12054 +465,94,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1993,12085 +350,561,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1993,12113 +991,143,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1993,12144 +910,95,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1993,12174 +206,341,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1993,12205 +263,388,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1993,12235 +374,272,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1993,12266 +875,890,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1993,12297 +810,734,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1993,12327 +398,364,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1993,12358 +565,619,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1993,12388 +417,517,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1994,12419 +291,781,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1994,12450 +251,327,GERMANY,WEST,EDUCATION,OFFICE,DESK,1,1994,12478 +449,48,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1994,12509 +774,809,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1994,12539 +386,73,GERMANY,WEST,EDUCATION,OFFICE,DESK,2,1994,12570 +22,936,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1994,12600 +940,400,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1994,12631 +132,736,GERMANY,WEST,EDUCATION,OFFICE,DESK,3,1994,12662 +103,211,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1994,12692 +152,271,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1994,12723 +952,855,GERMANY,WEST,EDUCATION,OFFICE,DESK,4,1994,12753 +872,923,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +748,854,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +749,769,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +876,271,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +860,383,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +900,29,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +705,185,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +913,351,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +315,560,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +466,840,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +233,517,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +906,949,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +148,633,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +661,636,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +847,138,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +768,481,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +866,408,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +475,130,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +112,813,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +136,661,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +763,311,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +388,872,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +996,643,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +486,174,GERMANY,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +494,528,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1993,12054 +771,124,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1993,12085 +49,126,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1993,12113 +322,440,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1993,12144 +878,881,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1993,12174 +827,292,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1993,12205 +852,873,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1993,12235 +716,357,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1993,12266 +81,247,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1993,12297 +916,18,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1993,12327 +673,395,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1993,12358 +242,620,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1993,12388 +914,946,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1994,12419 +902,72,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1994,12450 +707,691,GERMANY,WEST,CONSUMER,FURNITURE,BED,1,1994,12478 +223,95,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1994,12509 +619,878,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1994,12539 +254,757,GERMANY,WEST,CONSUMER,FURNITURE,BED,2,1994,12570 +688,898,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1994,12600 +477,172,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1994,12631 +280,419,GERMANY,WEST,CONSUMER,FURNITURE,BED,3,1994,12662 +546,849,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1994,12692 +630,807,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1994,12723 +455,599,GERMANY,WEST,CONSUMER,FURNITURE,BED,4,1994,12753 +505,59,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1993,12054 +823,790,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1993,12085 +891,574,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1993,12113 +840,96,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1993,12144 +436,376,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1993,12174 +168,352,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1993,12205 +177,741,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1993,12235 +727,12,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1993,12266 +278,157,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1993,12297 +443,10,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1993,12327 +905,544,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1993,12358 +881,817,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1993,12388 +507,754,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1994,12419 +363,425,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1994,12450 +603,492,GERMANY,WEST,CONSUMER,OFFICE,TABLE,1,1994,12478 +473,485,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1994,12509 +128,369,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1994,12539 +105,560,GERMANY,WEST,CONSUMER,OFFICE,TABLE,2,1994,12570 +325,651,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1994,12600 +711,326,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1994,12631 +983,180,GERMANY,WEST,CONSUMER,OFFICE,TABLE,3,1994,12662 +241,935,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1994,12692 +71,403,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1994,12723 +395,345,GERMANY,WEST,CONSUMER,OFFICE,TABLE,4,1994,12753 +168,278,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +512,376,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +291,104,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +776,543,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +271,798,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +946,333,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +195,833,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +165,132,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +238,629,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +409,337,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +720,300,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +309,470,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +812,875,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +441,237,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +500,272,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +517,860,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +924,415,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +572,140,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +768,367,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +692,195,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +28,245,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +202,285,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +76,98,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +421,932,GERMANY,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +636,898,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1993,12054 +52,330,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1993,12085 +184,603,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1993,12113 +739,280,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1993,12144 +841,507,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1993,12174 +65,202,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1993,12205 +623,513,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1993,12235 +517,132,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1993,12266 +636,21,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1993,12297 +845,657,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1993,12327 +232,195,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1993,12358 +26,323,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1993,12388 +680,299,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1994,12419 +364,811,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1994,12450 +572,739,GERMANY,WEST,CONSUMER,OFFICE,DESK,1,1994,12478 +145,889,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1994,12509 +644,189,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1994,12539 +87,698,GERMANY,WEST,CONSUMER,OFFICE,DESK,2,1994,12570 +620,646,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1994,12600 +535,562,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1994,12631 +661,753,GERMANY,WEST,CONSUMER,OFFICE,DESK,3,1994,12662 +884,425,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1994,12692 +689,693,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1994,12723 +646,941,GERMANY,WEST,CONSUMER,OFFICE,DESK,4,1994,12753 +4,975,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +813,455,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +773,260,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +205,69,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +657,147,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +154,533,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +747,881,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +787,457,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +867,441,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +307,859,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +571,177,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +92,633,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +269,382,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +764,707,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +662,566,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +818,349,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +617,128,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +649,231,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +895,258,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +750,812,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +738,362,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +107,133,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +278,60,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +32,88,U.S.A.,EAST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +129,378,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1993,12054 +187,569,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1993,12085 +670,186,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1993,12113 +678,875,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1993,12144 +423,636,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1993,12174 +389,360,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1993,12205 +257,677,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1993,12235 +780,708,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1993,12266 +159,158,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1993,12297 +97,384,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1993,12327 +479,927,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1993,12358 +9,134,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1993,12388 +614,273,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1994,12419 +261,27,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1994,12450 +115,209,U.S.A.,EAST,EDUCATION,FURNITURE,BED,1,1994,12478 +358,470,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1994,12509 +133,219,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1994,12539 +891,907,U.S.A.,EAST,EDUCATION,FURNITURE,BED,2,1994,12570 +702,778,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1994,12600 +58,998,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1994,12631 +606,194,U.S.A.,EAST,EDUCATION,FURNITURE,BED,3,1994,12662 +668,933,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1994,12692 +813,708,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1994,12723 +450,949,U.S.A.,EAST,EDUCATION,FURNITURE,BED,4,1994,12753 +956,579,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1993,12054 +276,131,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1993,12085 +889,689,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1993,12113 +708,908,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1993,12144 +14,524,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1993,12174 +904,336,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1993,12205 +272,916,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1993,12235 +257,236,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1993,12266 +343,965,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1993,12297 +80,350,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1993,12327 +530,599,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1993,12358 +340,901,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1993,12388 +595,935,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1994,12419 +47,667,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1994,12450 +279,104,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,1,1994,12478 +293,803,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1994,12509 +162,64,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1994,12539 +935,825,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,2,1994,12570 +689,839,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1994,12600 +484,184,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1994,12631 +230,348,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,3,1994,12662 +164,904,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1994,12692 +401,219,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1994,12723 +607,381,U.S.A.,EAST,EDUCATION,OFFICE,TABLE,4,1994,12753 +229,524,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +786,902,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +92,212,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +455,762,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +409,182,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +166,442,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +277,919,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +92,67,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +631,741,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +390,617,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +403,214,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +964,202,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +223,788,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +684,639,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +645,336,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +470,937,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +424,399,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +862,21,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +736,125,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +554,635,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +790,229,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +115,770,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +853,622,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +643,109,U.S.A.,EAST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +794,975,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1993,12054 +892,820,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1993,12085 +728,123,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1993,12113 +744,135,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1993,12144 +678,535,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1993,12174 +768,971,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1993,12205 +234,166,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1993,12235 +333,814,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1993,12266 +968,557,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1993,12297 +119,820,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1993,12327 +469,486,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1993,12358 +261,429,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1993,12388 +984,65,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1994,12419 +845,977,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1994,12450 +374,410,U.S.A.,EAST,EDUCATION,OFFICE,DESK,1,1994,12478 +687,150,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1994,12509 +157,630,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1994,12539 +49,488,U.S.A.,EAST,EDUCATION,OFFICE,DESK,2,1994,12570 +817,112,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1994,12600 +223,598,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1994,12631 +433,705,U.S.A.,EAST,EDUCATION,OFFICE,DESK,3,1994,12662 +41,226,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1994,12692 +396,979,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1994,12723 +131,19,U.S.A.,EAST,EDUCATION,OFFICE,DESK,4,1994,12753 +521,204,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +751,805,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +45,549,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +144,912,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +119,427,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +728,1,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +120,540,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +657,940,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +409,644,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +881,821,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +113,560,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +831,309,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +129,1000,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +76,945,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +260,931,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +882,504,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +157,950,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +443,278,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +111,225,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +497,6,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +321,124,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +194,206,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +684,320,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +634,270,U.S.A.,EAST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +622,278,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1993,12054 +689,447,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1993,12085 +120,170,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1993,12113 +374,87,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1993,12144 +926,384,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1993,12174 +687,574,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1993,12205 +600,585,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1993,12235 +779,947,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1993,12266 +223,984,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1993,12297 +628,189,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1993,12327 +326,364,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1993,12358 +836,49,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1993,12388 +361,851,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1994,12419 +444,643,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1994,12450 +501,143,U.S.A.,EAST,CONSUMER,FURNITURE,BED,1,1994,12478 +743,763,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1994,12509 +861,987,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1994,12539 +203,264,U.S.A.,EAST,CONSUMER,FURNITURE,BED,2,1994,12570 +762,439,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1994,12600 +705,750,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1994,12631 +153,37,U.S.A.,EAST,CONSUMER,FURNITURE,BED,3,1994,12662 +436,95,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1994,12692 +428,79,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1994,12723 +804,832,U.S.A.,EAST,CONSUMER,FURNITURE,BED,4,1994,12753 +805,649,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1993,12054 +860,838,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1993,12085 +104,439,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1993,12113 +434,207,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1993,12144 +912,804,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1993,12174 +571,875,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1993,12205 +267,473,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1993,12235 +415,845,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1993,12266 +261,91,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1993,12297 +746,630,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1993,12327 +30,185,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1993,12358 +662,317,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1993,12388 +916,88,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1994,12419 +415,607,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1994,12450 +514,35,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,1,1994,12478 +756,680,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1994,12509 +461,78,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1994,12539 +460,117,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,2,1994,12570 +305,440,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1994,12600 +198,652,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1994,12631 +234,249,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,3,1994,12662 +638,658,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1994,12692 +88,563,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1994,12723 +751,737,U.S.A.,EAST,CONSUMER,OFFICE,TABLE,4,1994,12753 +816,789,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +437,988,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +715,220,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +780,946,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +245,986,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +201,129,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +815,433,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +865,492,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +634,306,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +901,154,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +789,206,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +882,81,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +953,882,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +862,848,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +628,664,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +765,389,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +741,182,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +61,505,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +470,861,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +869,263,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +650,400,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +750,556,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +602,497,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +54,181,U.S.A.,EAST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +384,619,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1993,12054 +161,332,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1993,12085 +977,669,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1993,12113 +615,487,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1993,12144 +783,994,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1993,12174 +977,331,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1993,12205 +375,739,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1993,12235 +298,665,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1993,12266 +104,921,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1993,12297 +713,862,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1993,12327 +556,662,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1993,12358 +323,517,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1993,12388 +391,352,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1994,12419 +593,166,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1994,12450 +906,859,U.S.A.,EAST,CONSUMER,OFFICE,DESK,1,1994,12478 +130,571,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1994,12509 +613,976,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1994,12539 +58,466,U.S.A.,EAST,CONSUMER,OFFICE,DESK,2,1994,12570 +314,79,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1994,12600 +67,864,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1994,12631 +654,623,U.S.A.,EAST,CONSUMER,OFFICE,DESK,3,1994,12662 +312,170,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1994,12692 +349,662,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1994,12723 +415,763,U.S.A.,EAST,CONSUMER,OFFICE,DESK,4,1994,12753 +404,896,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12054 +22,973,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12085 +744,161,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1993,12113 +804,934,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12144 +101,697,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12174 +293,116,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1993,12205 +266,84,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12235 +372,604,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12266 +38,371,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1993,12297 +385,783,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12327 +262,335,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12358 +961,321,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1993,12388 +831,177,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12419 +579,371,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12450 +301,583,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,1,1994,12478 +693,364,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12509 +895,343,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12539 +320,854,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,2,1994,12570 +284,691,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12600 +362,387,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12631 +132,298,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,3,1994,12662 +42,635,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12692 +118,81,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12723 +42,375,U.S.A.,WEST,EDUCATION,FURNITURE,SOFA,4,1994,12753 +18,846,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1993,12054 +512,933,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1993,12085 +337,237,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1993,12113 +167,964,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1993,12144 +749,382,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1993,12174 +890,610,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1993,12205 +910,148,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1993,12235 +403,837,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1993,12266 +403,85,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1993,12297 +661,425,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1993,12327 +485,633,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1993,12358 +789,515,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1993,12388 +415,512,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1994,12419 +418,156,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1994,12450 +163,464,U.S.A.,WEST,EDUCATION,FURNITURE,BED,1,1994,12478 +298,813,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1994,12509 +584,455,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1994,12539 +797,366,U.S.A.,WEST,EDUCATION,FURNITURE,BED,2,1994,12570 +767,734,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1994,12600 +984,451,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1994,12631 +388,134,U.S.A.,WEST,EDUCATION,FURNITURE,BED,3,1994,12662 +924,547,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1994,12692 +566,802,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1994,12723 +390,61,U.S.A.,WEST,EDUCATION,FURNITURE,BED,4,1994,12753 +608,556,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1993,12054 +840,202,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1993,12085 +112,964,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1993,12113 +288,112,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1993,12144 +408,445,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1993,12174 +876,884,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1993,12205 +224,348,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1993,12235 +133,564,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1993,12266 +662,568,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1993,12297 +68,882,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1993,12327 +626,542,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1993,12358 +678,119,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1993,12388 +361,248,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1994,12419 +464,868,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1994,12450 +681,841,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,1,1994,12478 +377,484,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1994,12509 +222,986,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1994,12539 +972,39,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,2,1994,12570 +56,930,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1994,12600 +695,252,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1994,12631 +908,794,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,3,1994,12662 +328,658,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1994,12692 +891,139,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1994,12723 +265,331,U.S.A.,WEST,EDUCATION,OFFICE,TABLE,4,1994,12753 +251,261,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12054 +783,122,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12085 +425,296,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1993,12113 +859,391,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12144 +314,75,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12174 +153,731,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1993,12205 +955,883,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12235 +654,707,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12266 +693,97,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1993,12297 +757,390,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12327 +221,237,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12358 +942,496,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1993,12388 +31,814,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12419 +540,765,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12450 +352,308,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,1,1994,12478 +904,327,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12509 +436,266,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12539 +281,699,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,2,1994,12570 +801,599,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12600 +273,950,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12631 +716,117,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,3,1994,12662 +902,632,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12692 +341,35,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12723 +155,562,U.S.A.,WEST,EDUCATION,OFFICE,CHAIR,4,1994,12753 +796,144,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1993,12054 +257,142,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1993,12085 +611,273,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1993,12113 +6,915,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1993,12144 +125,920,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1993,12174 +745,294,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1993,12205 +437,681,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1993,12235 +906,86,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1993,12266 +844,764,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1993,12297 +413,269,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1993,12327 +869,138,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1993,12358 +403,834,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1993,12388 +137,112,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1994,12419 +922,921,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1994,12450 +202,859,U.S.A.,WEST,EDUCATION,OFFICE,DESK,1,1994,12478 +955,442,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1994,12509 +781,593,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1994,12539 +12,346,U.S.A.,WEST,EDUCATION,OFFICE,DESK,2,1994,12570 +931,312,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1994,12600 +95,690,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1994,12631 +795,344,U.S.A.,WEST,EDUCATION,OFFICE,DESK,3,1994,12662 +542,784,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1994,12692 +935,639,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1994,12723 +269,726,U.S.A.,WEST,EDUCATION,OFFICE,DESK,4,1994,12753 +197,596,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12054 +828,263,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12085 +461,194,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1993,12113 +35,895,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12144 +88,502,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12174 +832,342,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1993,12205 +900,421,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12235 +368,901,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12266 +201,474,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1993,12297 +758,571,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12327 +504,511,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12358 +864,379,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1993,12388 +574,68,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12419 +61,210,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12450 +565,478,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,1,1994,12478 +475,296,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12509 +44,664,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12539 +145,880,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,2,1994,12570 +813,607,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12600 +703,97,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12631 +757,908,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,3,1994,12662 +96,152,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12692 +860,622,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12723 +750,309,U.S.A.,WEST,CONSUMER,FURNITURE,SOFA,4,1994,12753 +585,912,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1993,12054 +127,429,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1993,12085 +669,580,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1993,12113 +708,179,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1993,12144 +830,119,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1993,12174 +550,369,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1993,12205 +762,882,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1993,12235 +468,727,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1993,12266 +151,823,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1993,12297 +103,783,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1993,12327 +876,884,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1993,12358 +881,891,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1993,12388 +116,909,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1994,12419 +677,765,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1994,12450 +477,180,U.S.A.,WEST,CONSUMER,FURNITURE,BED,1,1994,12478 +154,712,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1994,12509 +331,175,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1994,12539 +784,869,U.S.A.,WEST,CONSUMER,FURNITURE,BED,2,1994,12570 +563,820,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1994,12600 +229,554,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1994,12631 +451,126,U.S.A.,WEST,CONSUMER,FURNITURE,BED,3,1994,12662 +974,760,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1994,12692 +484,446,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1994,12723 +69,254,U.S.A.,WEST,CONSUMER,FURNITURE,BED,4,1994,12753 +755,516,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1993,12054 +331,779,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1993,12085 +482,987,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1993,12113 +632,318,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1993,12144 +750,427,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1993,12174 +618,86,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1993,12205 +935,553,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1993,12235 +716,315,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1993,12266 +205,328,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1993,12297 +215,521,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1993,12327 +871,156,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1993,12358 +552,841,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1993,12388 +619,623,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1994,12419 +701,849,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1994,12450 +104,438,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,1,1994,12478 +114,719,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1994,12509 +854,906,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1994,12539 +563,267,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,2,1994,12570 +73,542,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1994,12600 +427,552,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1994,12631 +348,428,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,3,1994,12662 +148,158,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1994,12692 +895,379,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1994,12723 +394,142,U.S.A.,WEST,CONSUMER,OFFICE,TABLE,4,1994,12753 +792,588,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12054 +175,506,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12085 +208,382,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1993,12113 +354,132,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12144 +163,652,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12174 +336,723,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1993,12205 +804,682,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12235 +863,382,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12266 +326,125,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1993,12297 +568,321,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12327 +691,922,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12358 +152,884,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1993,12388 +565,38,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12419 +38,194,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12450 +185,996,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,1,1994,12478 +318,532,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12509 +960,391,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12539 +122,104,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,2,1994,12570 +400,22,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12600 +301,650,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12631 +909,143,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,3,1994,12662 +433,999,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12692 +508,415,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12723 +648,350,U.S.A.,WEST,CONSUMER,OFFICE,CHAIR,4,1994,12753 +793,342,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1993,12054 +129,215,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1993,12085 +481,52,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1993,12113 +406,292,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1993,12144 +512,862,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1993,12174 +668,309,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1993,12205 +551,886,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1993,12235 +124,172,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1993,12266 +655,912,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1993,12297 +523,666,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1993,12327 +739,656,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1993,12358 +87,145,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1993,12388 +890,664,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1994,12419 +665,639,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1994,12450 +329,707,U.S.A.,WEST,CONSUMER,OFFICE,DESK,1,1994,12478 +417,891,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1994,12509 +828,466,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1994,12539 +298,451,U.S.A.,WEST,CONSUMER,OFFICE,DESK,2,1994,12570 +356,451,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1994,12600 +909,874,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1994,12631 +251,805,U.S.A.,WEST,CONSUMER,OFFICE,DESK,3,1994,12662 +526,426,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1994,12692 +652,932,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1994,12723 +573,581,U.S.A.,WEST,CONSUMER,OFFICE,DESK,4,1994,12753 diff --git a/pandas/io/tests/sas/data/productsales.sas7bdat b/pandas/io/tests/sas/data/productsales.sas7bdat new file mode 100644 index 0000000000000000000000000000000000000000..6f18c5a048115634347d0994b4879d5417beff29 GIT binary patch literal 148480 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 literal 0 HcmV?d00001 diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index a9e6ea68f3979..2f6ddfd60d5ec 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -62,3 +62,14 @@ def test_from_iterator(self): tm.assert_frame_equal(df, df0.iloc[0:2, :]) df = rdr.read(3) tm.assert_frame_equal(df, df0.iloc[2:5, :]) + + +def test_productsales(): + dirpath = tm.get_data_path() + fname = os.path.join(dirpath, "productsales.sas7bdat") + df = pd.read_sas(fname, encoding='utf-8') + fname = os.path.join(dirpath, "productsales.csv") + df0 = pd.read_csv(fname) + vn = ["ACTUAL", "PREDICT", "QUARTER", "YEAR", "MONTH"] + df0[vn] = df0[vn].astype(np.float64) + tm.assert_frame_equal(df, df0) From ea2339fc59ca770636ec0a367a82f82024beaa5c Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sat, 19 Mar 2016 07:28:41 -0400 Subject: [PATCH 03/19] Use encoding when reading column headers --- pandas/io/sas/sas7bdat.py | 5 ++- pandas/io/tests/sas/data/test_12659.csv | 37 +++++++++++++++++++ pandas/io/tests/sas/data/test_12659.sas7bdat | Bin 0 -> 131072 bytes pandas/io/tests/sas/test_sas7bdat.py | 10 +++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 pandas/io/tests/sas/data/test_12659.csv create mode 100644 pandas/io/tests/sas/data/test_12659.sas7bdat diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index a03ced57babe2..e9de502ed7d10 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -120,7 +120,8 @@ _compression_literals = [_rle_compression, _rdc_compression] -# Incomplete list of encodings +# Incomplete list of encodings, using SAS nomenclature: +# http://support.sas.com/documentation/cdl/en/nlsref/61893/HTML/default/viewer.htm#a002607278.htm _encoding_names = {29: "latin1", 20: "utf-8", 33: "cyrillic", 60: "wlatin2", 61: "wcyrillic", 62: "wlatin1", 90: "ebcdic870"} @@ -526,7 +527,7 @@ def _process_columntext_subheader(self, offset, length): buf = self._read_bytes(offset, text_block_size) self.column_names_strings.append( - buf[0:text_block_size].rstrip(b"\x00 ").decode()) + buf[0:text_block_size].rstrip(b"\x00 ").decode(self.encoding)) if len(self.column_names_strings) == 1: column_name = self.column_names_strings[0] diff --git a/pandas/io/tests/sas/data/test_12659.csv b/pandas/io/tests/sas/data/test_12659.csv new file mode 100644 index 0000000000000..cd83f7caa6aaf --- /dev/null +++ b/pandas/io/tests/sas/data/test_12659.csv @@ -0,0 +1,37 @@ +yearmonth,useGpCo,useGpVi,useSpec,useUrge,caseGpCo,caseGpVi,caseSpec,caseUrge,expendGpCo,expendGpVi,expendSpec,expendUrge +201401,11,12,13,14,15,16,17,18,19,20,21,22 +201402,11,12,13,14,15,16,17,18,19,20,21,22 +201403,11,12,13,14,15,16,17,18,19,20,21,22 +201404,11,12,13,14,15,16,17,18,19,20,21,22 +201405,11,12,13,14,15,16,17,18,19,20,21,22 +201406,11,12,13,14,15,16,17,18,19,20,21,22 +201407,11,12,13,14,15,16,17,18,19,20,21,22 +201408,11,12,13,14,15,16,17,18,19,20,21,22 +201409,11,12,13,14,15,16,17,18,19,20,21,22 +201410,11,12,13,14,15,16,17,18,19,20,21,22 +201411,11,12,13,14,15,16,17,18,19,20,21,22 +201412,11,12,13,14,15,16,17,18,19,20,21,22 +201501,11,12,13,14,15,16,17,18,19,20,21,22 +201502,11,12,13,14,15,16,17,18,19,20,21,22 +201503,11,12,13,14,15,16,17,18,19,20,21,22 +201504,11,12,13,14,15,16,17,18,19,20,21,22 +201505,11,12,13,14,15,16,17,18,19,20,21,22 +201506,11,12,13,14,15,16,17,18,19,20,21,22 +201507,11,12,13,14,15,16,17,18,19,20,21,22 +201508,11,12,13,14,15,16,17,18,19,20,21,22 +201509,11,12,13,14,15,16,17,18,19,20,21,22 +201510,11,12,13,14,15,16,17,18,19,20,21,22 +201511,11,12,13,14,15,16,17,18,19,20,21,22 +201512,11,12,13,14,15,16,17,18,19,20,21,22 +201601,11,12,13,14,15,16,17,18,19,20,21,22 +201602,11,12,13,14,15,16,17,18,19,20,21,22 +201603,11,12,13,14,15,16,17,18,19,20,21,22 +201604,11,12,13,14,15,16,17,18,19,20,21,22 +201605,11,12,13,14,15,16,17,18,19,20,21,22 +201606,11,12,13,14,15,16,17,18,19,20,21,22 +201607,11,12,13,14,15,16,17,18,19,20,21,22 +201608,11,12,13,14,15,16,17,18,19,20,21,22 +201609,11,12,13,14,15,16,17,18,19,20,21,22 +201610,11,12,13,14,15,16,17,18,19,20,21,22 +201611,11,12,13,14,15,16,17,18,19,20,21,22 +201612,11,12,13,14,15,16,17,18,19,20,21,22 diff --git a/pandas/io/tests/sas/data/test_12659.sas7bdat b/pandas/io/tests/sas/data/test_12659.sas7bdat new file mode 100644 index 0000000000000000000000000000000000000000..bf91e931aa64a160a229d73131a9fa97db05d67d GIT binary patch literal 131072 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 bdMDtCmN?C%XOHQlvMu>Lg}bO&#@qh@2Wg#f literal 0 HcmV?d00001 diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index 2f6ddfd60d5ec..d944ac8a19048 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -73,3 +73,13 @@ def test_productsales(): vn = ["ACTUAL", "PREDICT", "QUARTER", "YEAR", "MONTH"] df0[vn] = df0[vn].astype(np.float64) tm.assert_frame_equal(df, df0) + + +def test_12659(): + dirpath = tm.get_data_path() + fname = os.path.join(dirpath, "test_12659.sas7bdat") + df = pd.read_sas(fname, encoding='latin1') + fname = os.path.join(dirpath, "test_12659.csv") + df0 = pd.read_csv(fname) + df0 = df0.astype(np.float64) + tm.assert_frame_equal(df, df0) From bdc9a06c1661f24487a3b3773d22d75174935412 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sat, 19 Mar 2016 08:33:27 -0400 Subject: [PATCH 04/19] More cython for performance, refactored constants --- pandas/io/sas/sas7bdat.py | 445 +++++++++------------------------ pandas/io/sas/sas_constants.py | 146 +++++++++++ pandas/io/sas/saslib.pyx | 81 ++++++ 3 files changed, 347 insertions(+), 325 deletions(-) create mode 100644 pandas/io/sas/sas_constants.py diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index e9de502ed7d10..5c53c98a73e94 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -19,158 +19,8 @@ from pandas.io.common import get_filepath_or_buffer, BaseIterator import numpy as np import struct -from .saslib import (_rle_decompress, _rdc_decompress, - process_byte_array_with_data) - -_magic = (b"\x00\x00\x00\x00\x00\x00\x00\x00" + - b"\x00\x00\x00\x00\xc2\xea\x81\x60" + - b"\xb3\x14\x11\xcf\xbd\x92\x08\x00" + - b"\x09\xc7\x31\x8c\x18\x1f\x10\x11") - -_align_1_checker_value = b'3' -_align_1_offset = 32 -_align_1_length = 1 -_align_1_value = 4 -_u64_byte_checker_value = b'3' -_align_2_offset = 35 -_align_2_length = 1 -_align_2_value = 4 -_endianness_offset = 37 -_endianness_length = 1 -_platform_offset = 39 -_platform_length = 1 -_encoding_offset = 70 -_encoding_length = 1 -_dataset_offset = 92 -_dataset_length = 64 -_file_type_offset = 156 -_file_type_length = 8 -_date_created_offset = 164 -_date_created_length = 8 -_date_modified_offset = 172 -_date_modified_length = 8 -_header_size_offset = 196 -_header_size_length = 4 -_page_size_offset = 200 -_page_size_length = 4 -_page_count_offset = 204 -_page_count_length = 4 -_sas_release_offset = 216 -_sas_release_length = 8 -_sas_server_type_offset = 224 -_sas_server_type_length = 16 -_os_version_number_offset = 240 -_os_version_number_length = 16 -_os_maker_offset = 256 -_os_maker_length = 16 -_os_name_offset = 272 -_os_name_length = 16 -_page_bit_offset_x86 = 16 -_page_bit_offset_x64 = 32 -_subheader_pointer_length_x86 = 12 -_subheader_pointer_length_x64 = 24 -_page_type_offset = 0 -_page_type_length = 2 -_block_count_offset = 2 -_block_count_length = 2 -_subheader_count_offset = 4 -_subheader_count_length = 2 -_page_meta_type = 0 -_page_data_type = 256 -_page_amd_type = 1024 -_page_metc_type = 16384 -_page_comp_type = -28672 -_page_mix_types = [512, 640] -_subheader_pointers_offset = 8 -_truncated_subheader_id = 1 -_compressed_subheader_id = 4 -_compressed_subheader_type = 1 -_text_block_size_length = 2 -_row_length_offset_multiplier = 5 -_row_count_offset_multiplier = 6 -_col_count_p1_multiplier = 9 -_col_count_p2_multiplier = 10 -_row_count_on_mix_page_offset_multiplier = 15 -_column_name_pointer_length = 8 -_column_name_text_subheader_offset = 0 -_column_name_text_subheader_length = 2 -_column_name_offset_offset = 2 -_column_name_offset_length = 2 -_column_name_length_offset = 4 -_column_name_length_length = 2 -_column_data_offset_offset = 8 -_column_data_length_offset = 8 -_column_data_length_length = 4 -_column_type_offset = 14 -_column_type_length = 1 -_column_format_text_subheader_index_offset = 22 -_column_format_text_subheader_index_length = 2 -_column_format_offset_offset = 24 -_column_format_offset_length = 2 -_column_format_length_offset = 26 -_column_format_length_length = 2 -_column_label_text_subheader_index_offset = 28 -_column_label_text_subheader_index_length = 2 -_column_label_offset_offset = 30 -_column_label_offset_length = 2 -_column_label_length_offset = 32 -_column_label_length_length = 2 -_rle_compression = 'SASYZCRL' -_rdc_compression = 'SASYZCR2' - -_compression_literals = [_rle_compression, _rdc_compression] - -# Incomplete list of encodings, using SAS nomenclature: -# http://support.sas.com/documentation/cdl/en/nlsref/61893/HTML/default/viewer.htm#a002607278.htm -_encoding_names = {29: "latin1", 20: "utf-8", 33: "cyrillic", 60: "wlatin2", - 61: "wcyrillic", 62: "wlatin1", 90: "ebcdic870"} - -# Should be enum - - -class _index: - rowSizeIndex = 0 - columnSizeIndex = 1 - subheaderCountsIndex = 2 - columnTextIndex = 3 - columnNameIndex = 4 - columnAttributesIndex = 5 - formatAndLabelIndex = 6 - columnListIndex = 7 - dataSubheaderIndex = 8 - - -_subheader_signature_to_index = { - b"\xF7\xF7\xF7\xF7": _index.rowSizeIndex, - b"\x00\x00\x00\x00\xF7\xF7\xF7\xF7": _index.rowSizeIndex, - b"\xF7\xF7\xF7\xF7\x00\x00\x00\x00": _index.rowSizeIndex, - b"\xF7\xF7\xF7\xF7\xFF\xFF\xFB\xFE": _index.rowSizeIndex, - b"\xF6\xF6\xF6\xF6": _index.columnSizeIndex, - b"\x00\x00\x00\x00\xF6\xF6\xF6\xF6": _index.columnSizeIndex, - b"\xF6\xF6\xF6\xF6\x00\x00\x00\x00": _index.columnSizeIndex, - b"\xF6\xF6\xF6\xF6\xFF\xFF\xFB\xFE": _index.columnSizeIndex, - b"\x00\xFC\xFF\xFF": _index.subheaderCountsIndex, - b"\xFF\xFF\xFC\x00": _index.subheaderCountsIndex, - b"\x00\xFC\xFF\xFF\xFF\xFF\xFF\xFF": _index.subheaderCountsIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00": _index.subheaderCountsIndex, - b"\xFD\xFF\xFF\xFF": _index.columnTextIndex, - b"\xFF\xFF\xFF\xFD": _index.columnTextIndex, - b"\xFD\xFF\xFF\xFF\xFF\xFF\xFF\xFF": _index.columnTextIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFD": _index.columnTextIndex, - b"\xFF\xFF\xFF\xFF": _index.columnNameIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF": _index.columnNameIndex, - b"\xFC\xFF\xFF\xFF": _index.columnAttributesIndex, - b"\xFF\xFF\xFF\xFC": _index.columnAttributesIndex, - b"\xFC\xFF\xFF\xFF\xFF\xFF\xFF\xFF": _index.columnAttributesIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC": _index.columnAttributesIndex, - b"\xFE\xFB\xFF\xFF": _index.formatAndLabelIndex, - b"\xFF\xFF\xFB\xFE": _index.formatAndLabelIndex, - b"\xFE\xFB\xFF\xFF\xFF\xFF\xFF\xFF": _index.formatAndLabelIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFB\xFE": _index.formatAndLabelIndex, - b"\xFE\xFF\xFF\xFF": _index.columnListIndex, - b"\xFF\xFF\xFF\xFE": _index.columnListIndex, - b"\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF": _index.columnListIndex, - b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE": _index.columnListIndex} +import pandas.io.sas.sas_constants as const +from .saslib import (_rle_decompress, _rdc_decompress, _readline) class _subheader_pointer(object): @@ -242,44 +92,45 @@ def _get_properties(self): # Check magic number self._path_or_buf.seek(0) self._cached_page = self._path_or_buf.read(288) - if self._cached_page[0:len(_magic)] != _magic: + if self._cached_page[0:len(const.magic)] != const.magic: raise ValueError("magic number mismatch (not a SAS file?)") # Get alignment information align1, align2 = 0, 0 - buf = self._read_bytes(_align_1_offset, _align_1_length) - if buf == _u64_byte_checker_value: - align2 = _align_2_value + buf = self._read_bytes(const.align_1_offset, const.align_1_length) + if buf == const.u64_byte_checker_value: + align2 = const.align_2_value self.U64 = True self._int_length = 8 - self._page_bit_offset = _page_bit_offset_x64 - self._subheader_pointer_length = _subheader_pointer_length_x64 + self._page_bit_offset = const.page_bit_offset_x64 + self._subheader_pointer_length = const.subheader_pointer_length_x64 else: self.U64 = False - self._page_bit_offset = _page_bit_offset_x86 - self._subheader_pointer_length = _subheader_pointer_length_x86 + self._page_bit_offset = const.page_bit_offset_x86 + self._subheader_pointer_length = const.subheader_pointer_length_x86 self._int_length = 4 - buf = self._read_bytes(_align_2_offset, _align_2_length) - if buf == _align_1_checker_value: - align1 = _align_2_value + buf = self._read_bytes(const.align_2_offset, const.align_2_length) + if buf == const.align_1_checker_value: + align1 = const.align_2_value total_align = align1 + align2 # Get endianness information - buf = self._read_bytes(_endianness_offset, _endianness_length) + buf = self._read_bytes(const.endianness_offset, + const.endianness_length) if buf == b'\x01': self.byte_order = "<" else: self.byte_order = ">" # Get encoding information - buf = self._read_bytes(_encoding_offset, _encoding_length)[0] - if buf in _encoding_names: - self.file_encoding = _encoding_names[buf] + buf = self._read_bytes(const.encoding_offset, const.encoding_length)[0] + if buf in const.encoding_names: + self.file_encoding = const.encoding_names[buf] else: self.file_encoding = "unknown (code=%s)" % str(buf) # Get platform information - buf = self._read_bytes(_platform_offset, _platform_length) + buf = self._read_bytes(const.platform_offset, const.platform_length) if buf == b'1': self.platform = "unix" elif buf == b'2': @@ -287,23 +138,23 @@ def _get_properties(self): else: self.platform = "unknown" - buf = self._read_bytes(_dataset_offset, _dataset_length) + buf = self._read_bytes(const.dataset_offset, const.dataset_length) self.name = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes(_file_type_offset, _file_type_length) + buf = self._read_bytes(const.file_type_offset, const.file_type_length) self.file_type = buf.rstrip(b'\x00 ').decode() # Timestamp is epoch 01/01/1960 epoch = pd.datetime(1960, 1, 1) - x = self._read_float(_date_created_offset + align1, - _date_created_length) + x = self._read_float(const.date_created_offset + align1, + const.date_created_length) self.date_created = epoch + pd.to_timedelta(x, unit='s') - x = self._read_float(_date_modified_offset + align1, - _date_modified_length) + x = self._read_float(const.date_modified_offset + align1, + const.date_modified_length) self.date_modified = epoch + pd.to_timedelta(x, unit='s') - self.header_length = self._read_int(_header_size_offset + align1, - _header_size_length) + self.header_length = self._read_int(const.header_size_offset + align1, + const.header_size_length) # Read the rest of the header into cached_page. buf = self._path_or_buf.read(self.header_length - 288) @@ -311,29 +162,30 @@ def _get_properties(self): if len(self._cached_page) != self.header_length: raise ValueError("The SAS7BDAT file appears to be truncated.") - self._page_length = self._read_int(_page_size_offset + align1, - _page_size_length) - self._page_count = self._read_int(_page_count_offset + align1, - _page_count_length) + self._page_length = self._read_int(const.page_size_offset + align1, + const.page_size_length) + self._page_count = self._read_int(const.page_count_offset + align1, + const.page_count_length) - buf = self._read_bytes(_sas_release_offset + total_align, - _sas_release_length) + buf = self._read_bytes(const.sas_release_offset + total_align, + const.sas_release_length) self.sas_release = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes(_sas_server_type_offset + total_align, - _sas_server_type_length) + buf = self._read_bytes(const.sas_server_type_offset + total_align, + const.sas_server_type_length) self.server_type = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes(_os_version_number_offset + total_align, - _os_version_number_length) + buf = self._read_bytes(const.os_version_number_offset + total_align, + const.os_version_number_length) self.os_version = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes(_os_name_offset, _os_name_length) + buf = self._read_bytes(const.os_name_offset, const.os_name_length) buf = buf.rstrip(b'\x00 ') if len(buf) > 0: self.os_name = buf.decode() else: - buf = self._read_bytes(_os_maker_offset, _os_maker_length) + buf = self._read_bytes(const.os_maker_offset, + const.os_maker_length) self.os_name = buf.rstrip(b'\x00 ').decode() # Read a single float of the given width (4 or 8). @@ -379,32 +231,32 @@ def _parse_metadata(self): def _process_page_meta(self): self._read_page_header() - pt = [_page_meta_type, _page_amd_type] + _page_mix_types + pt = [const.page_meta_type, const.page_amd_type] + const.page_mix_types if self._current_page_type in pt: self._process_page_metadata() - return ((self._current_page_type in [256] + _page_mix_types) or + return ((self._current_page_type in [256] + const.page_mix_types) or (self._current_page_data_subheader_pointers is not None)) def _read_page_header(self): bit_offset = self._page_bit_offset - tx = _page_type_offset + bit_offset - self._current_page_type = self._read_int(tx, _page_type_length) - tx = _block_count_offset + bit_offset - self._current_page_block_count = self._read_int(tx, - _block_count_length) - tx = _subheader_count_offset + bit_offset + tx = const.page_type_offset + bit_offset + self._current_page_type = self._read_int(tx, const.page_type_length) + tx = const.block_count_offset + bit_offset + self._current_page_block_count = self._read_int( + tx, const.block_count_length) + tx = const.subheader_count_offset + bit_offset self._current_page_subheaders_count = ( - self._read_int(tx, _subheader_count_length)) + self._read_int(tx, const.subheader_count_length)) def _process_page_metadata(self): bit_offset = self._page_bit_offset for i in range(self._current_page_subheaders_count): pointer = self._process_subheader_pointers( - _subheader_pointers_offset + bit_offset, i) + const.subheader_pointers_offset + bit_offset, i) if pointer.length == 0: continue - if pointer.compression == _truncated_subheader_id: + if pointer.compression == const.truncated_subheader_id: continue subheader_signature = self._read_subheader_signature( pointer.offset) @@ -414,13 +266,13 @@ def _process_page_metadata(self): self._process_subheader(subheader_index, pointer) def _get_subheader_index(self, signature, compression, ptype): - index = _subheader_signature_to_index.get(signature) + index = const.subheader_signature_to_index.get(signature) if index is None: - f1 = ((compression == _compressed_subheader_id) or + f1 = ((compression == const.compressed_subheader_id) or (compression == 0)) - f2 = (ptype == _compressed_subheader_type) + f2 = (ptype == const.compressed_subheader_type) if (self.compression != "") and f1 and f2: - index = _index.dataSubheaderIndex + index = const.index.dataSubheaderIndex else: raise ValueError("Unknown subheader signature") return index @@ -458,23 +310,23 @@ def _process_subheader(self, subheader_index, pointer): offset = pointer.offset length = pointer.length - if subheader_index == _index.rowSizeIndex: + if subheader_index == const.index.rowSizeIndex: processor = self._process_rowsize_subheader - elif subheader_index == _index.columnSizeIndex: + elif subheader_index == const.index.columnSizeIndex: processor = self._process_columnsize_subheader - elif subheader_index == _index.columnTextIndex: + elif subheader_index == const.index.columnTextIndex: processor = self._process_columntext_subheader - elif subheader_index == _index.columnNameIndex: + elif subheader_index == const.index.columnNameIndex: processor = self._process_columnname_subheader - elif subheader_index == _index.columnAttributesIndex: + elif subheader_index == const.index.columnAttributesIndex: processor = self._process_columnattributes_subheader - elif subheader_index == _index.formatAndLabelIndex: + elif subheader_index == const.index.formatAndLabelIndex: processor = self._process_format_subheader - elif subheader_index == _index.columnListIndex: + elif subheader_index == const.index.columnListIndex: processor = self._process_columnlist_subheader - elif subheader_index == _index.subheaderCountsIndex: + elif subheader_index == const.index.subheaderCountsIndex: processor = self._process_subheader_counts - elif subheader_index == _index.dataSubheaderIndex: + elif subheader_index == const.index.dataSubheaderIndex: self._current_page_data_subheader_pointers.append(pointer) return else: @@ -495,14 +347,14 @@ def _process_rowsize_subheader(self, offset, length): lcp_offset += 378 self.row_length = self._read_int( - offset + _row_length_offset_multiplier * int_len, int_len) + offset + const.row_length_offset_multiplier * int_len, int_len) self.row_count = self._read_int( - offset + _row_count_offset_multiplier * int_len, int_len) + offset + const.row_count_offset_multiplier * int_len, int_len) self.col_count_p1 = self._read_int( - offset + _col_count_p1_multiplier * int_len, int_len) + offset + const.col_count_p1_multiplier * int_len, int_len) self.col_count_p2 = self._read_int( - offset + _col_count_p2_multiplier * int_len, int_len) - mx = _row_count_on_mix_page_offset_multiplier * int_len + offset + const.col_count_p2_multiplier * int_len, int_len) + mx = const.row_count_on_mix_page_offset_multiplier * int_len self._mix_page_row_count = self._read_int(offset + mx, int_len) self._lcs = self._read_int(lcs_offset, 2) self._lcp = self._read_int(lcp_offset, 2) @@ -523,7 +375,7 @@ def _process_subheader_counts(self, offset, length): def _process_columntext_subheader(self, offset, length): offset += self._int_length - text_block_size = self._read_int(offset, _text_block_size_length) + text_block_size = self._read_int(offset, const.text_block_size_length) buf = self._read_bytes(offset, text_block_size) self.column_names_strings.append( @@ -532,7 +384,7 @@ def _process_columntext_subheader(self, offset, length): if len(self.column_names_strings) == 1: column_name = self.column_names_strings[0] compression_literal = "" - for cl in _compression_literals: + for cl in const.compression_literals: if cl in column_name: compression_literal = cl self.compression = compression_literal @@ -551,7 +403,7 @@ def _process_columntext_subheader(self, offset, length): offset1 += 4 buf = self._read_bytes(offset1, self._lcp) self.creator_proc = buf[0:self._lcp].decode() - elif compression_literal == _rle_compression: + elif compression_literal == const.rle_compression: offset1 = offset + 40 if self.U64: offset1 += 4 @@ -570,19 +422,19 @@ def _process_columnname_subheader(self, offset, length): offset += int_len column_name_pointers_count = (length - 2 * int_len - 12) // 8 for i in range(column_name_pointers_count): - text_subheader = offset + _column_name_pointer_length * \ - (i + 1) + _column_name_text_subheader_offset - col_name_offset = offset + _column_name_pointer_length * \ - (i + 1) + _column_name_offset_offset - col_name_length = offset + _column_name_pointer_length * \ - (i + 1) + _column_name_length_offset + text_subheader = offset + const.column_name_pointer_length * \ + (i + 1) + const.column_name_text_subheader_offset + col_name_offset = offset + const.column_name_pointer_length * \ + (i + 1) + const.column_name_offset_offset + col_name_length = offset + const.column_name_pointer_length * \ + (i + 1) + const.column_name_length_offset idx = self._read_int( - text_subheader, _column_name_text_subheader_length) + text_subheader, const.column_name_text_subheader_length) col_offset = self._read_int( - col_name_offset, _column_name_offset_length) + col_name_offset, const.column_name_offset_length) col_len = self._read_int( - col_name_length, _column_name_length_length) + col_name_length, const.column_name_length_length) name_str = self.column_names_strings[idx] self.column_names.append(name_str[col_offset:col_offset + col_len]) @@ -599,19 +451,21 @@ def _process_columnattributes_subheader(self, offset, length): column_attributes_vectors_count, dtype=np.int64) for i in range(column_attributes_vectors_count): col_data_offset = (offset + int_len + - _column_data_offset_offset + i * (int_len + 8)) + const.column_data_offset_offset + + i * (int_len + 8)) col_data_len = (offset + 2 * int_len + - _column_data_length_offset + i * (int_len + 8)) + const.column_data_length_offset + + i * (int_len + 8)) col_types = (offset + 2 * int_len + - _column_type_offset + i * (int_len + 8)) + const.column_type_offset + i * (int_len + 8)) x = self._read_int(col_data_offset, int_len) self._column_data_offsets[i] = x - x = self._read_int(col_data_len, _column_data_length_length) + x = self._read_int(col_data_len, const.column_data_length_length) self._column_data_lengths[i] = x - x = self._read_int(col_types, _column_type_length) + x = self._read_int(col_types, const.column_type_length) if x == 1: self.column_types[i] = b'd' else: @@ -623,31 +477,43 @@ def _process_columnlist_subheader(self, offset, length): def _process_format_subheader(self, offset, length): int_len = self._int_length - text_subheader_format = offset + \ - _column_format_text_subheader_index_offset + 3 * int_len - col_format_offset = offset + _column_format_offset_offset + 3 * int_len - col_format_len = offset + _column_format_length_offset + 3 * int_len - text_subheader_label = offset + \ - _column_label_text_subheader_index_offset + 3 * int_len - col_label_offset = offset + _column_label_offset_offset + 3 * int_len - col_label_len = offset + _column_label_length_offset + 3 * int_len + text_subheader_format = ( + offset + + const.column_format_text_subheader_index_offset + + 3 * int_len) + col_format_offset = (offset + + const.column_format_offset_offset + + 3 * int_len) + col_format_len = (offset + + const.column_format_length_offset + + 3 * int_len) + text_subheader_label = ( + offset + + const.column_label_text_subheader_index_offset + + 3 * int_len) + col_label_offset = (offset + + const.column_label_offset_offset + + 3 * int_len) + col_label_len = offset + const.column_label_length_offset + 3 * int_len x = self._read_int(text_subheader_format, - _column_format_text_subheader_index_length) + const.column_format_text_subheader_index_length) format_idx = min(x, len(self.column_names_strings) - 1) format_start = self._read_int( - col_format_offset, _column_format_offset_length) + col_format_offset, const.column_format_offset_length) format_len = self._read_int( - col_format_len, _column_format_length_length) + col_format_len, const.column_format_length_length) label_idx = self._read_int( - text_subheader_label, _column_label_text_subheader_index_length) + text_subheader_label, + const.column_label_text_subheader_index_length) label_idx = min(label_idx, len(self.column_names_strings) - 1) label_start = self._read_int( - col_label_offset, _column_label_offset_length) - label_len = self._read_int(col_label_len, _column_label_length_length) + col_label_offset, const.column_label_offset_length) + label_len = self._read_int(col_label_len, + const.column_label_length_length) label_names = self.column_names_strings[label_idx] column_label = label_names[label_start: label_start + label_len] @@ -684,7 +550,7 @@ def read(self, nrows=None): self._current_row_in_chunk_index = 0 for i in range(nrows): - done = self._readline() + done = _readline(self) if done: break @@ -694,78 +560,6 @@ def read(self, nrows=None): return rslt - def _readline(self): - - bit_offset = self._page_bit_offset - subheader_pointer_length = self._subheader_pointer_length - - # If there is no page, go to the end of the header and read a page. - if self._cached_page is None: - self._path_or_buf.seek(self.header_length) - done = self._read_next_page() - if done: - return True - - # Loop until a data row is read - while True: - if self._current_page_type == _page_meta_type: - flag = (self._current_row_on_page_index >= - len(self._current_page_data_subheader_pointers)) - if flag: - done = self._read_next_page() - if done: - return True - self._current_row_on_page_index = 0 - continue - current_subheader_pointer = ( - self._current_page_data_subheader_pointers[ - self._current_row_on_page_index]) - process_byte_array_with_data(self, - current_subheader_pointer.offset, - current_subheader_pointer.length, - self._byte_chunk, - self._string_chunk) - return False - elif self._current_page_type in _page_mix_types: - align_correction = (bit_offset + _subheader_pointers_offset + - self._current_page_subheaders_count * - subheader_pointer_length) - align_correction = align_correction % 8 - offset = bit_offset + align_correction - offset += _subheader_pointers_offset - offset += (self._current_page_subheaders_count * - subheader_pointer_length) - offset += self._current_row_on_page_index * self.row_length - process_byte_array_with_data(self, offset, self.row_length, - self._byte_chunk, - self._string_chunk) - mn = min(self.row_count, self._mix_page_row_count) - if self._current_row_on_page_index == mn: - done = self._read_next_page() - if done: - return True - self._current_row_on_page_index = 0 - return False - elif self._current_page_type == _page_data_type: - process_byte_array_with_data(self, - bit_offset + - _subheader_pointers_offset + - self._current_row_on_page_index * - self.row_length, - self.row_length, self._byte_chunk, - self._string_chunk) - flag = (self._current_row_on_page_index == - self._current_page_block_count) - if flag: - done = self._read_next_page() - if done: - return True - self._current_row_on_page_index = 0 - return False - else: - raise ValueError("unknown page type: %s", - self._current_page_type) - def _read_next_page(self): self._current_page_data_subheader_pointers = [] self._cached_page = self._path_or_buf.read(self._page_length) @@ -778,9 +572,10 @@ def _read_next_page(self): self._page_length)) self._read_page_header() - if self._current_page_type == _page_meta_type: + if self._current_page_type == const.page_meta_type: self._process_page_metadata() - pt = [_page_meta_type, _page_data_type] + [_page_mix_types] + pt = [const.page_meta_type, const.page_data_type] + pt += [const.page_mix_types] if self._current_page_type not in pt: return self._read_next_page() @@ -788,9 +583,9 @@ def _read_next_page(self): def _decompress(self, row_length, page): page = np.frombuffer(page, dtype=np.uint8) - if self.compression == _rle_compression: + if self.compression == const.rle_compression: return _rle_decompress(row_length, page) - elif self.compression == _rdc_compression: + elif self.compression == const.rdc_compression: return _rdc_decompress(row_length, page) else: raise ValueError("unknown SAS compression method: %s" % diff --git a/pandas/io/sas/sas_constants.py b/pandas/io/sas/sas_constants.py new file mode 100644 index 0000000000000..62e1b112476ba --- /dev/null +++ b/pandas/io/sas/sas_constants.py @@ -0,0 +1,146 @@ +magic = (b"\x00\x00\x00\x00\x00\x00\x00\x00" + + b"\x00\x00\x00\x00\xc2\xea\x81\x60" + + b"\xb3\x14\x11\xcf\xbd\x92\x08\x00" + + b"\x09\xc7\x31\x8c\x18\x1f\x10\x11") + +align_1_checker_value = b'3' +align_1_offset = 32 +align_1_length = 1 +align_1_value = 4 +u64_byte_checker_value = b'3' +align_2_offset = 35 +align_2_length = 1 +align_2_value = 4 +endianness_offset = 37 +endianness_length = 1 +platform_offset = 39 +platform_length = 1 +encoding_offset = 70 +encoding_length = 1 +dataset_offset = 92 +dataset_length = 64 +file_type_offset = 156 +file_type_length = 8 +date_created_offset = 164 +date_created_length = 8 +date_modified_offset = 172 +date_modified_length = 8 +header_size_offset = 196 +header_size_length = 4 +page_size_offset = 200 +page_size_length = 4 +page_count_offset = 204 +page_count_length = 4 +sas_release_offset = 216 +sas_release_length = 8 +sas_server_type_offset = 224 +sas_server_type_length = 16 +os_version_number_offset = 240 +os_version_number_length = 16 +os_maker_offset = 256 +os_maker_length = 16 +os_name_offset = 272 +os_name_length = 16 +page_bit_offset_x86 = 16 +page_bit_offset_x64 = 32 +subheader_pointer_length_x86 = 12 +subheader_pointer_length_x64 = 24 +page_type_offset = 0 +page_type_length = 2 +block_count_offset = 2 +block_count_length = 2 +subheader_count_offset = 4 +subheader_count_length = 2 +page_meta_type = 0 +page_data_type = 256 +page_amd_type = 1024 +page_metc_type = 16384 +page_comp_type = -28672 +page_mix_types = [512, 640] +subheader_pointers_offset = 8 +truncated_subheader_id = 1 +compressed_subheader_id = 4 +compressed_subheader_type = 1 +text_block_size_length = 2 +row_length_offset_multiplier = 5 +row_count_offset_multiplier = 6 +col_count_p1_multiplier = 9 +col_count_p2_multiplier = 10 +row_count_on_mix_page_offset_multiplier = 15 +column_name_pointer_length = 8 +column_name_text_subheader_offset = 0 +column_name_text_subheader_length = 2 +column_name_offset_offset = 2 +column_name_offset_length = 2 +column_name_length_offset = 4 +column_name_length_length = 2 +column_data_offset_offset = 8 +column_data_length_offset = 8 +column_data_length_length = 4 +column_type_offset = 14 +column_type_length = 1 +column_format_text_subheader_index_offset = 22 +column_format_text_subheader_index_length = 2 +column_format_offset_offset = 24 +column_format_offset_length = 2 +column_format_length_offset = 26 +column_format_length_length = 2 +column_label_text_subheader_index_offset = 28 +column_label_text_subheader_index_length = 2 +column_label_offset_offset = 30 +column_label_offset_length = 2 +column_label_length_offset = 32 +column_label_length_length = 2 +rle_compression = 'SASYZCRL' +rdc_compression = 'SASYZCR2' + +compression_literals = [rle_compression, rdc_compression] + +# Incomplete list of encodings, using SAS nomenclature: +# http://support.sas.com/documentation/cdl/en/nlsref/61893/HTML/default/viewer.htm#a002607278.htm +encoding_names = {29: "latin1", 20: "utf-8", 33: "cyrillic", 60: "wlatin2", + 61: "wcyrillic", 62: "wlatin1", 90: "ebcdic870"} + +class index: + rowSizeIndex = 0 + columnSizeIndex = 1 + subheaderCountsIndex = 2 + columnTextIndex = 3 + columnNameIndex = 4 + columnAttributesIndex = 5 + formatAndLabelIndex = 6 + columnListIndex = 7 + dataSubheaderIndex = 8 + + +subheader_signature_to_index = { + b"\xF7\xF7\xF7\xF7": index.rowSizeIndex, + b"\x00\x00\x00\x00\xF7\xF7\xF7\xF7": index.rowSizeIndex, + b"\xF7\xF7\xF7\xF7\x00\x00\x00\x00": index.rowSizeIndex, + b"\xF7\xF7\xF7\xF7\xFF\xFF\xFB\xFE": index.rowSizeIndex, + b"\xF6\xF6\xF6\xF6": index.columnSizeIndex, + b"\x00\x00\x00\x00\xF6\xF6\xF6\xF6": index.columnSizeIndex, + b"\xF6\xF6\xF6\xF6\x00\x00\x00\x00": index.columnSizeIndex, + b"\xF6\xF6\xF6\xF6\xFF\xFF\xFB\xFE": index.columnSizeIndex, + b"\x00\xFC\xFF\xFF": index.subheaderCountsIndex, + b"\xFF\xFF\xFC\x00": index.subheaderCountsIndex, + b"\x00\xFC\xFF\xFF\xFF\xFF\xFF\xFF": index.subheaderCountsIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00": index.subheaderCountsIndex, + b"\xFD\xFF\xFF\xFF": index.columnTextIndex, + b"\xFF\xFF\xFF\xFD": index.columnTextIndex, + b"\xFD\xFF\xFF\xFF\xFF\xFF\xFF\xFF": index.columnTextIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFD": index.columnTextIndex, + b"\xFF\xFF\xFF\xFF": index.columnNameIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF": index.columnNameIndex, + b"\xFC\xFF\xFF\xFF": index.columnAttributesIndex, + b"\xFF\xFF\xFF\xFC": index.columnAttributesIndex, + b"\xFC\xFF\xFF\xFF\xFF\xFF\xFF\xFF": index.columnAttributesIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC": index.columnAttributesIndex, + b"\xFE\xFB\xFF\xFF": index.formatAndLabelIndex, + b"\xFF\xFF\xFB\xFE": index.formatAndLabelIndex, + b"\xFE\xFB\xFF\xFF\xFF\xFF\xFF\xFF": index.formatAndLabelIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFB\xFE": index.formatAndLabelIndex, + b"\xFE\xFF\xFF\xFF": index.columnListIndex, + b"\xFF\xFF\xFF\xFE": index.columnListIndex, + b"\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF": index.columnListIndex, + b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE": index.columnListIndex} diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index bfaff79dcb30e..3dd63d10c929b 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -1,6 +1,8 @@ import numpy as np cimport numpy as np from numpy cimport uint8_t, uint16_t, int8_t +import sas_constants as const + # rle_decompress decompresses data using a Run Length Encoding # algorithm. It is partially documented here: @@ -191,6 +193,85 @@ def _rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): return np.asarray(outbuff).tostring() + +def _readline(parser): + + cdef int offset + cdef int bit_offset + cdef int align_correction + cdef int subheader_pointer_length + + bit_offset = parser._page_bit_offset + subheader_pointer_length = parser._subheader_pointer_length + + # If there is no page, go to the end of the header and read a page. + if parser._cached_page is None: + parser._path_or_buf.seek(parser.header_length) + done = parser._read_next_page() + if done: + return True + + # Loop until a data row is read + while True: + if parser._current_page_type == const.page_meta_type: + flag = (parser._current_row_on_page_index >= + len(parser._current_page_data_subheader_pointers)) + if flag: + done = parser._read_next_page() + if done: + return True + parser._current_row_on_page_index = 0 + continue + current_subheader_pointer = ( + parser._current_page_data_subheader_pointers[ + parser._current_row_on_page_index]) + process_byte_array_with_data(parser, + current_subheader_pointer.offset, + current_subheader_pointer.length, + parser._byte_chunk, + parser._string_chunk) + return False + elif parser._current_page_type in const.page_mix_types: + align_correction = (bit_offset + const.subheader_pointers_offset + + parser._current_page_subheaders_count * + subheader_pointer_length) + align_correction = align_correction % 8 + offset = bit_offset + align_correction + offset += const.subheader_pointers_offset + offset += (parser._current_page_subheaders_count * + subheader_pointer_length) + offset += parser._current_row_on_page_index * parser.row_length + process_byte_array_with_data(parser, offset, parser.row_length, + parser._byte_chunk, + parser._string_chunk) + mn = min(parser.row_count, parser._mix_page_row_count) + if parser._current_row_on_page_index == mn: + done = parser._read_next_page() + if done: + return True + parser._current_row_on_page_index = 0 + return False + elif parser._current_page_type == const.page_data_type: + process_byte_array_with_data(parser, + bit_offset + + const.subheader_pointers_offset + + parser._current_row_on_page_index * + parser.row_length, + parser.row_length, parser._byte_chunk, + parser._string_chunk) + flag = (parser._current_row_on_page_index == + parser._current_page_block_count) + if flag: + done = parser._read_next_page() + if done: + return True + parser._current_row_on_page_index = 0 + return False + else: + raise ValueError("unknown page type: %s", + parser._current_page_type) + + def process_byte_array_with_data(parser, int offset, int length, uint8_t[:, ::1] byte_chunk, object[:, ::1] string_chunk): From 3bd1b35cc9a27f76b0f9458d703bcc394a91e5bd Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sat, 19 Mar 2016 08:53:08 -0400 Subject: [PATCH 05/19] Move more code to cython --- pandas/io/sas/sas7bdat.py | 7 ++----- pandas/io/sas/saslib.pyx | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 5c53c98a73e94..d20edf7635e0f 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -20,7 +20,7 @@ import numpy as np import struct import pandas.io.sas.sas_constants as const -from .saslib import (_rle_decompress, _rdc_decompress, _readline) +from .saslib import (_rle_decompress, _rdc_decompress, _do_read) class _subheader_pointer(object): @@ -549,10 +549,7 @@ def read(self, nrows=None): self._byte_chunk = np.empty((nd, 8 * nrows), dtype=np.uint8) self._current_row_in_chunk_index = 0 - for i in range(nrows): - done = _readline(self) - if done: - break + _do_read(self, nrows) rslt = self._chunk_to_dataframe() if self.index is not None: diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 3dd63d10c929b..d866fe5c102ec 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -194,6 +194,15 @@ def _rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): return np.asarray(outbuff).tostring() +def _do_read(parser, int nrows): + cdef int i + + for i in range(nrows): + done = _readline(parser) + if done: + break + + def _readline(parser): cdef int offset From 23bdf7a49c80341c7cfa83f5c146e4ceac6dbfa7 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sat, 19 Mar 2016 09:02:22 -0400 Subject: [PATCH 06/19] Decouple data decoding and decoding e.g. of column names --- pandas/io/sas/sas7bdat.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index d20edf7635e0f..bd48c11ef649c 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -53,17 +53,21 @@ class SAS7BDATReader(BaseIterator): Return SAS7BDATReader object for iterations, returns chunks with given number of lines. encoding : string, defaults to None - String encoding. If None, text variables are left as raw bytes. + String encoding. + convert_text : bool, deafaults to True + If False, text variables are left as raw bytes. """ def __init__(self, path_or_buf, index=None, convert_dates=True, - blank_missing=True, chunksize=None, encoding=None): + blank_missing=True, chunksize=None, encoding=None, + convert_text=True): self.index = index self.convert_dates = convert_dates self.blank_missing = blank_missing self.chunksize = chunksize self.encoding = encoding + self.convert_text = convert_text self.compression = "" self.column_names_strings = [] @@ -611,7 +615,7 @@ def _chunk_to_dataframe(self): elif self.column_types[j] == b's': rslt[name] = self._string_chunk[js, :] rslt[name] = rslt[name].apply(lambda x: x.rstrip(b'\x00 ')) - if self.encoding is not None: + if self.convert_text and (self.encoding is not None): rslt[name] = rslt[name].apply( lambda x: x.decode(encoding=self.encoding)) if self.blank_missing: From dc330c56be33e30e87629fc6a51ec0ebf898bf30 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sat, 19 Mar 2016 10:12:17 -0400 Subject: [PATCH 07/19] Add two missing alignment constants --- pandas/io/sas/sas7bdat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index bd48c11ef649c..150f0e0984c48 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -183,12 +183,13 @@ def _get_properties(self): const.os_version_number_length) self.os_version = buf.rstrip(b'\x00 ').decode() - buf = self._read_bytes(const.os_name_offset, const.os_name_length) + buf = self._read_bytes(const.os_name_offset + total_align, + const.os_name_length) buf = buf.rstrip(b'\x00 ') if len(buf) > 0: self.os_name = buf.decode() else: - buf = self._read_bytes(const.os_maker_offset, + buf = self._read_bytes(const.os_maker_offset + total_align, const.os_maker_length) self.os_name = buf.rstrip(b'\x00 ').decode() From 7e156b7e5a96390fa39884d7f1c16e5234ff0bd8 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sun, 10 Apr 2016 01:10:19 -0400 Subject: [PATCH 08/19] Working on cython issues --- pandas/io/sas/sas7bdat.py | 25 +++------ pandas/io/sas/saslib.pyx | 107 ++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 73 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 150f0e0984c48..edd89f094f3f1 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -20,7 +20,7 @@ import numpy as np import struct import pandas.io.sas.sas_constants as const -from .saslib import (_rle_decompress, _rdc_decompress, _do_read) +from pandas.io.sas.saslib import do_read class _subheader_pointer(object): @@ -550,11 +550,14 @@ def read(self, nrows=None): nd = (self.column_types == b'd').sum() ns = (self.column_types == b's').sum() - self._string_chunk = np.empty((ns, nrows), dtype=np.object) + self._string_chunk = [] + for j,ct in enumerate(self.column_types): + if ct == b's': + self._string_chunk.append([None] * nrows) self._byte_chunk = np.empty((nd, 8 * nrows), dtype=np.uint8) self._current_row_in_chunk_index = 0 - _do_read(self, nrows) + do_read(self, nrows) rslt = self._chunk_to_dataframe() if self.index is not None: @@ -583,16 +586,6 @@ def _read_next_page(self): return False - def _decompress(self, row_length, page): - page = np.frombuffer(page, dtype=np.uint8) - if self.compression == const.rle_compression: - return _rle_decompress(row_length, page) - elif self.compression == const.rdc_compression: - return _rdc_decompress(row_length, page) - else: - raise ValueError("unknown SAS compression method: %s" % - self.compression) - def _chunk_to_dataframe(self): n = self._current_row_in_chunk_index @@ -614,11 +607,9 @@ def _chunk_to_dataframe(self): rslt[name] = epoch + pd.to_timedelta(rslt[name], unit='d') jb += 1 elif self.column_types[j] == b's': - rslt[name] = self._string_chunk[js, :] - rslt[name] = rslt[name].apply(lambda x: x.rstrip(b'\x00 ')) + rslt[name] = pd.Series(self._string_chunk[js], dtype=np.object) if self.convert_text and (self.encoding is not None): - rslt[name] = rslt[name].apply( - lambda x: x.decode(encoding=self.encoding)) + rslt[name] = rslt[name].str.decode(self.encoding) if self.blank_missing: ii = rslt[name].str.len() == 0 rslt.loc[ii, name] = np.nan diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index d866fe5c102ec..52abfa58bb496 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -8,17 +8,15 @@ import sas_constants as const # algorithm. It is partially documented here: # # https://cran.r-project.org/web/packages/sas7bdat/vignettes/sas7bdat.pdf -def _rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): +cdef np.ndarray[uint8_t, ndim=1] rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - cdef uint8_t control_byte - cdef uint8_t [:] result = np.zeros(result_length, np.uint8) - - cdef int rpos = 0 - cdef int ipos = 0 - cdef int i - cdef int nbytes - cdef uint8_t x - cdef length = len(inbuff) + cdef: + uint8_t control_byte, x + np.ndarray[uint8_t, ndim=1] result = np.zeros(result_length, np.uint8) + int rpos = 0 + int ipos = 0 + int i, nbytes + length = len(inbuff) while ipos < length: control_byte = inbuff[ipos] & 0xF0 @@ -107,24 +105,22 @@ def _rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): if len(result) != result_length: print("RLE: %v != %v\n", (len(result), result_length)) - return np.asarray(result).tostring() + return np.asarray(result, dtype=np.uint8) # rdc_decompress decompresses data using the Ross Data Compression algorithm: # # http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/CUJ/1992/9210/ross/ross.htm -def _rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - - cdef uint8_t cmd - cdef uint16_t ctrl_bits - cdef uint16_t ctrl_mask = 0 - cdef uint16_t ofs - cdef uint16_t cnt - cdef int ipos = 0 - cdef int rpos = 0 - cdef int k +cdef np.ndarray[uint8_t, ndim=1] rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - cdef uint8_t [:] outbuff = np.zeros(result_length, dtype=np.uint8) + cdef: + uint8_t cmd, ofs, cnt + uint16_t ctrl_bits + uint16_t ctrl_mask = 0 + int ipos = 0 + int rpos = 0 + int k + np.ndarray[uint8_t, ndim=1] outbuff = np.zeros(result_length, dtype=np.uint8) ii = -1 @@ -191,24 +187,33 @@ def _rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): if len(outbuff) != result_length: raise ValueError("RDC: %v != %v\n", len(outbuff), result_length) - return np.asarray(outbuff).tostring() + return np.asarray(outbuff, dtype=np.uint8) -def _do_read(parser, int nrows): +cdef np.ndarray[uint8_t, ndim=1] decompress(object parser, int row_length, uint8_t[:] page): + page = np.frombuffer(page, dtype=np.uint8) + if parser.compression == const.rle_compression: + return rle_decompress(row_length, page) + elif parser.compression == const.rdc_compression: + return rdc_decompress(row_length, page) + else: + raise ValueError("unknown SAS compression method: %s" % + parser.compression) + + +def do_read(object parser, int nrows): cdef int i for i in range(nrows): - done = _readline(parser) + done = readline(parser) if done: break -def _readline(parser): +cdef bint readline(object parser): - cdef int offset - cdef int bit_offset - cdef int align_correction - cdef int subheader_pointer_length + cdef: + int offset, bit_offset, align_correction, subheader_pointer_length bit_offset = parser._page_bit_offset subheader_pointer_length = parser._subheader_pointer_length @@ -236,9 +241,7 @@ def _readline(parser): parser._current_row_on_page_index]) process_byte_array_with_data(parser, current_subheader_pointer.offset, - current_subheader_pointer.length, - parser._byte_chunk, - parser._string_chunk) + current_subheader_pointer.length) return False elif parser._current_page_type in const.page_mix_types: align_correction = (bit_offset + const.subheader_pointers_offset + @@ -250,9 +253,7 @@ def _readline(parser): offset += (parser._current_page_subheaders_count * subheader_pointer_length) offset += parser._current_row_on_page_index * parser.row_length - process_byte_array_with_data(parser, offset, parser.row_length, - parser._byte_chunk, - parser._string_chunk) + process_byte_array_with_data(parser, offset, parser.row_length) mn = min(parser.row_count, parser._mix_page_row_count) if parser._current_row_on_page_index == mn: done = parser._read_next_page() @@ -266,8 +267,7 @@ def _readline(parser): const.subheader_pointers_offset + parser._current_row_on_page_index * parser.row_length, - parser.row_length, parser._byte_chunk, - parser._string_chunk) + parser.row_length) flag = (parser._current_row_on_page_index == parser._current_page_block_count) if flag: @@ -281,25 +281,20 @@ def _readline(parser): parser._current_page_type) -def process_byte_array_with_data(parser, int offset, int length, uint8_t[:, ::1] byte_chunk, - object[:, ::1] string_chunk): - - cdef int s - cdef int j - cdef int k - cdef int m - cdef int start - cdef int jb - cdef int js - cdef int lngt +cdef void process_byte_array_with_data(object parser, int offset, int length): - cdef long[:] lengths = parser._column_data_lengths - cdef long[:] offsets = parser._column_data_offsets - cdef char[:] column_types = parser.column_types + cdef: + int s, j, k, m, start, jb, js, lngt + long[:] lengths = parser._column_data_lengths + long[:] offsets = parser._column_data_offsets + char[:] column_types = parser.column_types + uint8_t[:, :] byte_chunk = parser._byte_chunk + #object[:, :] string_chunk = parser._string_chunk - source = parser._cached_page[offset:offset+length] + source = np.frombuffer(parser._cached_page[offset:offset+length], dtype=np.uint8) if (parser.compression != "") and (length < parser.row_length): - source = parser._decompress(parser.row_length, source) + source = decompress(parser, parser.row_length, source) + return s = 8 * parser._current_row_in_chunk_index js = 0 @@ -318,10 +313,10 @@ def process_byte_array_with_data(parser, int offset, int length, uint8_t[:, ::1] byte_chunk[jb, m + k] = source[start + k] jb += 1 elif column_types[j] == b's': - string_chunk[js, parser._current_row_in_chunk_index] = bytes(source[start:start+lngt]) + parser._string_chunk[js][parser._current_row_in_chunk_index] = source[start:(start+lngt)].tostring().rstrip() js += 1 else: - raise ValueError("unknown column type: %s" % parser.columns[j].ctype) + raise ValueError("unknown column type: %s" % parser.columns[j].ctype) parser._current_row_on_page_index += 1 parser._current_row_in_chunk_index += 1 From 3ef626e5dd882b4e57e3b7c0f1f8ce7fa7a6d3ef Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sun, 10 Apr 2016 01:12:44 -0400 Subject: [PATCH 09/19] Working on cython issues --- pandas/io/sas/saslib.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 52abfa58bb496..3bd1ac037025c 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -294,7 +294,6 @@ cdef void process_byte_array_with_data(object parser, int offset, int length): source = np.frombuffer(parser._cached_page[offset:offset+length], dtype=np.uint8) if (parser.compression != "") and (length < parser.row_length): source = decompress(parser, parser.row_length, source) - return s = 8 * parser._current_row_in_chunk_index js = 0 From 11c2f310808f7fd65f2756c6a1ebcc170a4c9119 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sun, 10 Apr 2016 12:05:56 -0400 Subject: [PATCH 10/19] Further cythonization --- pandas/io/sas/sas7bdat.py | 7 ++--- pandas/io/sas/saslib.pyx | 56 ++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index edd89f094f3f1..574d3af1e4b3e 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -550,10 +550,7 @@ def read(self, nrows=None): nd = (self.column_types == b'd').sum() ns = (self.column_types == b's').sum() - self._string_chunk = [] - for j,ct in enumerate(self.column_types): - if ct == b's': - self._string_chunk.append([None] * nrows) + self._string_chunk = np.empty((ns, nrows), dtype=np.object) self._byte_chunk = np.empty((nd, 8 * nrows), dtype=np.uint8) self._current_row_in_chunk_index = 0 @@ -607,7 +604,7 @@ def _chunk_to_dataframe(self): rslt[name] = epoch + pd.to_timedelta(rslt[name], unit='d') jb += 1 elif self.column_types[j] == b's': - rslt[name] = pd.Series(self._string_chunk[js], dtype=np.object) + rslt[name] = self._string_chunk[js, :] if self.convert_text and (self.encoding is not None): rslt[name] = rslt[name].str.decode(self.encoding) if self.blank_missing: diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 3bd1ac037025c..2a1ebff851791 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -3,20 +3,21 @@ cimport numpy as np from numpy cimport uint8_t, uint16_t, int8_t import sas_constants as const - # rle_decompress decompresses data using a Run Length Encoding # algorithm. It is partially documented here: # # https://cran.r-project.org/web/packages/sas7bdat/vignettes/sas7bdat.pdf -cdef np.ndarray[uint8_t, ndim=1] rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): +cdef rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - cdef: - uint8_t control_byte, x - np.ndarray[uint8_t, ndim=1] result = np.zeros(result_length, np.uint8) - int rpos = 0 - int ipos = 0 - int i, nbytes - length = len(inbuff) + cdef uint8_t control_byte + cdef uint8_t [:] result = np.zeros(result_length, np.uint8) + + cdef int rpos = 0 + cdef int ipos = 0 + cdef int i + cdef int nbytes + cdef uint8_t x + cdef length = len(inbuff) while ipos < length: control_byte = inbuff[ipos] & 0xF0 @@ -105,22 +106,24 @@ cdef np.ndarray[uint8_t, ndim=1] rle_decompress(int result_length, np.ndarray[ui if len(result) != result_length: print("RLE: %v != %v\n", (len(result), result_length)) - return np.asarray(result, dtype=np.uint8) + return np.asarray(result).tostring() # rdc_decompress decompresses data using the Ross Data Compression algorithm: # # http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/CUJ/1992/9210/ross/ross.htm -cdef np.ndarray[uint8_t, ndim=1] rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): +cdef rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - cdef: - uint8_t cmd, ofs, cnt - uint16_t ctrl_bits - uint16_t ctrl_mask = 0 - int ipos = 0 - int rpos = 0 - int k - np.ndarray[uint8_t, ndim=1] outbuff = np.zeros(result_length, dtype=np.uint8) + cdef uint8_t cmd + cdef uint16_t ctrl_bits + cdef uint16_t ctrl_mask = 0 + cdef uint16_t ofs + cdef uint16_t cnt + cdef int ipos = 0 + cdef int rpos = 0 + cdef int k + + cdef uint8_t [:] outbuff = np.zeros(result_length, dtype=np.uint8) ii = -1 @@ -187,10 +190,9 @@ cdef np.ndarray[uint8_t, ndim=1] rdc_decompress(int result_length, np.ndarray[ui if len(outbuff) != result_length: raise ValueError("RDC: %v != %v\n", len(outbuff), result_length) - return np.asarray(outbuff, dtype=np.uint8) - + return np.asarray(outbuff).tostring() -cdef np.ndarray[uint8_t, ndim=1] decompress(object parser, int row_length, uint8_t[:] page): +cdef decompress(object parser, int row_length, page): page = np.frombuffer(page, dtype=np.uint8) if parser.compression == const.rle_compression: return rle_decompress(row_length, page) @@ -210,7 +212,7 @@ def do_read(object parser, int nrows): break -cdef bint readline(object parser): +cdef readline(object parser): cdef: int offset, bit_offset, align_correction, subheader_pointer_length @@ -281,7 +283,7 @@ cdef bint readline(object parser): parser._current_page_type) -cdef void process_byte_array_with_data(object parser, int offset, int length): +cdef process_byte_array_with_data(object parser, int offset, int length): cdef: int s, j, k, m, start, jb, js, lngt @@ -289,9 +291,9 @@ cdef void process_byte_array_with_data(object parser, int offset, int length): long[:] offsets = parser._column_data_offsets char[:] column_types = parser.column_types uint8_t[:, :] byte_chunk = parser._byte_chunk - #object[:, :] string_chunk = parser._string_chunk + object[:, :] string_chunk = parser._string_chunk - source = np.frombuffer(parser._cached_page[offset:offset+length], dtype=np.uint8) + source = parser._cached_page[offset:offset+length] if (parser.compression != "") and (length < parser.row_length): source = decompress(parser, parser.row_length, source) @@ -312,7 +314,7 @@ cdef void process_byte_array_with_data(object parser, int offset, int length): byte_chunk[jb, m + k] = source[start + k] jb += 1 elif column_types[j] == b's': - parser._string_chunk[js][parser._current_row_in_chunk_index] = source[start:(start+lngt)].tostring().rstrip() + string_chunk[js, parser._current_row_in_chunk_index] = source[start:(start+lngt)].rstrip() js += 1 else: raise ValueError("unknown column type: %s" % parser.columns[j].ctype) From 873a877ef946ddf006eb0edd408f3f5bff9a4682 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Sun, 10 Apr 2016 19:43:12 -0400 Subject: [PATCH 11/19] Added option to not decode header text --- pandas/io/sas/sas7bdat.py | 40 ++++++++++++++++++++-------- pandas/io/sas/saslib.pyx | 15 ++++++----- pandas/io/tests/sas/test_sas7bdat.py | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 574d3af1e4b3e..d4b5a15cb36f4 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -54,13 +54,16 @@ class SAS7BDATReader(BaseIterator): with given number of lines. encoding : string, defaults to None String encoding. - convert_text : bool, deafaults to True + convert_text : bool, defaults to True If False, text variables are left as raw bytes. + convert_header_text : bool, defaults to True + If False, header text, including column names, are left as raw + bytes. """ def __init__(self, path_or_buf, index=None, convert_dates=True, blank_missing=True, chunksize=None, encoding=None, - convert_text=True): + convert_text=True, convert_header_text=True): self.index = index self.convert_dates = convert_dates @@ -68,6 +71,7 @@ def __init__(self, path_or_buf, index=None, convert_dates=True, self.chunksize = chunksize self.encoding = encoding self.convert_text = convert_text + self.convert_header_text = convert_header_text self.compression = "" self.column_names_strings = [] @@ -143,10 +147,14 @@ def _get_properties(self): self.platform = "unknown" buf = self._read_bytes(const.dataset_offset, const.dataset_length) - self.name = buf.rstrip(b'\x00 ').decode() + self.name = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.name = self.name.decode(self.encoding) buf = self._read_bytes(const.file_type_offset, const.file_type_length) - self.file_type = buf.rstrip(b'\x00 ').decode() + self.file_type = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.file_type = self.file_type.decode(self.encoding) # Timestamp is epoch 01/01/1960 epoch = pd.datetime(1960, 1, 1) @@ -173,25 +181,33 @@ def _get_properties(self): buf = self._read_bytes(const.sas_release_offset + total_align, const.sas_release_length) - self.sas_release = buf.rstrip(b'\x00 ').decode() + self.sas_release = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.sas_release = self.sas_release.decode(self.encoding) buf = self._read_bytes(const.sas_server_type_offset + total_align, const.sas_server_type_length) - self.server_type = buf.rstrip(b'\x00 ').decode() + self.server_type = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.server_type = self.server_type.decode(self.encoding) buf = self._read_bytes(const.os_version_number_offset + total_align, const.os_version_number_length) - self.os_version = buf.rstrip(b'\x00 ').decode() + self.os_version = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.os_version = self.os_version.decode(self.encoding) buf = self._read_bytes(const.os_name_offset + total_align, const.os_name_length) buf = buf.rstrip(b'\x00 ') if len(buf) > 0: - self.os_name = buf.decode() + self.os_name = buf.decode(self.encoding) else: buf = self._read_bytes(const.os_maker_offset + total_align, const.os_maker_length) - self.os_name = buf.rstrip(b'\x00 ').decode() + self.os_name = buf.rstrip(b'\x00 ') + if self.convert_header_text: + self.os_name = self.os_name.decode(self.encoding) # Read a single float of the given width (4 or 8). def _read_float(self, offset, width): @@ -383,8 +399,10 @@ def _process_columntext_subheader(self, offset, length): text_block_size = self._read_int(offset, const.text_block_size_length) buf = self._read_bytes(offset, text_block_size) - self.column_names_strings.append( - buf[0:text_block_size].rstrip(b"\x00 ").decode(self.encoding)) + cname = buf[0:text_block_size].rstrip(b"\x00 ") + if self.convert_header_text: + cname = cname.decode(self.encoding) + self.column_names_strings.append(cname) if len(self.column_names_strings) == 1: column_name = self.column_names_strings[0] diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 2a1ebff851791..989a733cece6e 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -11,7 +11,6 @@ cdef rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): cdef uint8_t control_byte cdef uint8_t [:] result = np.zeros(result_length, np.uint8) - cdef int rpos = 0 cdef int ipos = 0 cdef int i @@ -106,7 +105,7 @@ cdef rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): if len(result) != result_length: print("RLE: %v != %v\n", (len(result), result_length)) - return np.asarray(result).tostring() + return np.asarray(result) # rdc_decompress decompresses data using the Ross Data Compression algorithm: @@ -122,7 +121,6 @@ cdef rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): cdef int ipos = 0 cdef int rpos = 0 cdef int k - cdef uint8_t [:] outbuff = np.zeros(result_length, dtype=np.uint8) ii = -1 @@ -190,7 +188,7 @@ cdef rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): if len(outbuff) != result_length: raise ValueError("RDC: %v != %v\n", len(outbuff), result_length) - return np.asarray(outbuff).tostring() + return np.asarray(outbuff) cdef decompress(object parser, int row_length, page): page = np.frombuffer(page, dtype=np.uint8) @@ -292,10 +290,13 @@ cdef process_byte_array_with_data(object parser, int offset, int length): char[:] column_types = parser.column_types uint8_t[:, :] byte_chunk = parser._byte_chunk object[:, :] string_chunk = parser._string_chunk + np.ndarray[uint8_t, ndim=1] source + np.ndarray[uint8_t, ndim=1] raw_source = np.frombuffer(parser._cached_page[offset:offset+length], dtype=np.uint8) - source = parser._cached_page[offset:offset+length] if (parser.compression != "") and (length < parser.row_length): - source = decompress(parser, parser.row_length, source) + source = decompress(parser, parser.row_length, raw_source) + else: + source = raw_source s = 8 * parser._current_row_in_chunk_index js = 0 @@ -314,7 +315,7 @@ cdef process_byte_array_with_data(object parser, int offset, int length): byte_chunk[jb, m + k] = source[start + k] jb += 1 elif column_types[j] == b's': - string_chunk[js, parser._current_row_in_chunk_index] = source[start:(start+lngt)].rstrip() + string_chunk[js, parser._current_row_in_chunk_index] = source[start:(start+lngt)].tostring().rstrip() js += 1 else: raise ValueError("unknown column type: %s" % parser.columns[j].ctype) diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index d944ac8a19048..7da55da7be141 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -47,7 +47,7 @@ def test_from_buffer(self): byts = open(fname, 'rb').read() buf = io.BytesIO(byts) df = pd.read_sas(buf, format="sas7bdat", encoding='utf-8') - tm.assert_frame_equal(df, df0) + tm.assert_frame_equal(df, df0, check_exact=False) def test_from_iterator(self): for j in 0, 1: From c26d22b8dafa9a6e552ac3f75eee503c41533b9d Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Mon, 11 Apr 2016 08:50:08 -0400 Subject: [PATCH 12/19] added to whatsnew --- doc/source/whatsnew/v0.18.1.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index 928fefd6ce17e..abb193f0eb331 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -248,7 +248,7 @@ Deprecations Performance Improvements ~~~~~~~~~~~~~~~~~~~~~~~~ - +- Improved speed of SAS reader (PR 12656) - Improved performance of ``DataFrame.to_sql`` when checking case sensitivity for tables. Now only checks if table has been created correctly when table name is not lower case. (:issue:`12876`) @@ -281,6 +281,7 @@ Bug Fixes - Bug in ``.drop()`` with a non-unique ``MultiIndex``. (:issue:`12701`) - Bug in ``.concat`` of datetime tz-aware and naive DataFrames (:issue:`12467`) - Bug in correctly raising a ``ValueError`` in ``.resample(..).fillna(..)`` when passing a non-string (:issue:`12952`) +- Various encoding and header processing issues in SAS reader (:issue:`12659`, :issue:`12654`, :issue:`12647`, :issue:`12809`, :issue:`12656`) - Bug in ``Timestamp.__repr__`` that caused ``pprint`` to fail in nested structures (:issue:`12622`) - Bug in ``Timedelta.min`` and ``Timedelta.max``, the properties now report the true minimum/maximum ``timedeltas`` as recognized by Pandas. See :ref:`documentation `. (:issue:`12727`) From 1af73b3a50288aaf97bc41b86b58a48fc0885443 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Mon, 11 Apr 2016 09:32:45 -0400 Subject: [PATCH 13/19] Further encoding work --- pandas/io/sas/sas7bdat.py | 30 ++++++++++++++++------------ pandas/io/tests/sas/test_sas7bdat.py | 19 ++++++++++++++++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index d4b5a15cb36f4..8dd6b1691865e 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -73,6 +73,7 @@ def __init__(self, path_or_buf, index=None, convert_dates=True, self.convert_text = convert_text self.convert_header_text = convert_header_text + self.default_encoding = "latin-1" self.compression = "" self.column_names_strings = [] self.column_names = [] @@ -149,12 +150,12 @@ def _get_properties(self): buf = self._read_bytes(const.dataset_offset, const.dataset_length) self.name = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.name = self.name.decode(self.encoding) + self.name = self.name.decode(self.encoding or self.default_encoding) buf = self._read_bytes(const.file_type_offset, const.file_type_length) self.file_type = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.file_type = self.file_type.decode(self.encoding) + self.file_type = self.file_type.decode(self.encoding or self.default_encoding) # Timestamp is epoch 01/01/1960 epoch = pd.datetime(1960, 1, 1) @@ -183,31 +184,31 @@ def _get_properties(self): const.sas_release_length) self.sas_release = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.sas_release = self.sas_release.decode(self.encoding) + self.sas_release = self.sas_release.decode(self.encoding or self.default_encoding) buf = self._read_bytes(const.sas_server_type_offset + total_align, const.sas_server_type_length) self.server_type = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.server_type = self.server_type.decode(self.encoding) + self.server_type = self.server_type.decode(self.encoding or self.default_encoding) buf = self._read_bytes(const.os_version_number_offset + total_align, const.os_version_number_length) self.os_version = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.os_version = self.os_version.decode(self.encoding) + self.os_version = self.os_version.decode(self.encoding or self.default_encoding) buf = self._read_bytes(const.os_name_offset + total_align, const.os_name_length) buf = buf.rstrip(b'\x00 ') if len(buf) > 0: - self.os_name = buf.decode(self.encoding) + self.os_name = buf.decode(self.encoding or self.default_encoding) else: buf = self._read_bytes(const.os_maker_offset + total_align, const.os_maker_length) self.os_name = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.os_name = self.os_name.decode(self.encoding) + self.os_name = self.os_name.decode(self.encoding or self.default_encoding) # Read a single float of the given width (4 or 8). def _read_float(self, offset, width): @@ -401,14 +402,14 @@ def _process_columntext_subheader(self, offset, length): buf = self._read_bytes(offset, text_block_size) cname = buf[0:text_block_size].rstrip(b"\x00 ") if self.convert_header_text: - cname = cname.decode(self.encoding) + cname = cname.decode(self.encoding or self.default_encoding) self.column_names_strings.append(cname) if len(self.column_names_strings) == 1: column_name = self.column_names_strings[0] compression_literal = "" for cl in const.compression_literals: - if cl in column_name: + if cl in str(column_name): compression_literal = cl self.compression = compression_literal offset -= self._int_length @@ -425,20 +426,23 @@ def _process_columntext_subheader(self, offset, length): if self.U64: offset1 += 4 buf = self._read_bytes(offset1, self._lcp) - self.creator_proc = buf[0:self._lcp].decode() + self.creator_proc = buf[0:self._lcp] elif compression_literal == const.rle_compression: offset1 = offset + 40 if self.U64: offset1 += 4 buf = self._read_bytes(offset1, self._lcp) - self.creator_proc = buf[0:self._lcp].decode() + self.creator_proc = buf[0:self._lcp] elif self._lcs > 0: self._lcp = 0 offset1 = offset + 16 if self.U64: offset1 += 4 buf = self._read_bytes(offset1, self._lcs) - self.creator_proc = buf[0:self._lcp].decode() + self.creator_proc = buf[0:self._lcp] + if self.convert_header_text: + if hasattr(self, "creator_proc"): + self.creator_proc = self.creator_proc.decode(self.encoding or self.default_encoding) def _process_columnname_subheader(self, offset, length): int_len = self._int_length @@ -624,7 +628,7 @@ def _chunk_to_dataframe(self): elif self.column_types[j] == b's': rslt[name] = self._string_chunk[js, :] if self.convert_text and (self.encoding is not None): - rslt[name] = rslt[name].str.decode(self.encoding) + rslt[name] = rslt[name].str.decode(self.encoding or self.default_encoding) if self.blank_missing: ii = rslt[name].str.len() == 0 rslt.loc[ii, name] = np.nan diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index 7da55da7be141..4fcf186ae58bb 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -64,6 +64,25 @@ def test_from_iterator(self): tm.assert_frame_equal(df, df0.iloc[2:5, :]) +def test_encoding_options(): + dirpath = tm.get_data_path() + fname = os.path.join(dirpath, "test1.sas7bdat") + df1 = pd.read_sas(fname) + df2 = pd.read_sas(fname, encoding='utf-8') + for col in df1.columns: + try: + df1[col] = df1[col].str.decode('utf-8') + except AttributeError: + pass + tm.assert_frame_equal(df1, df2) + + from pandas.io.sas.sas7bdat import SAS7BDATReader + rdr = SAS7BDATReader(fname, convert_header_text=False) + df3 = rdr.read() + for x,y in zip(df1.columns, df3.columns): + assert(x == y.decode()) + + def test_productsales(): dirpath = tm.get_data_path() fname = os.path.join(dirpath, "productsales.sas7bdat") From 8b4b96d4e2dd8db89f4adfd3cf335e955939105a Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Mon, 11 Apr 2016 09:38:30 -0400 Subject: [PATCH 14/19] pep8 cleanup --- pandas/io/sas/sas7bdat.py | 24 ++++++++++++++++-------- pandas/io/tests/sas/test_sas7bdat.py | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 8dd6b1691865e..1dcedd523f6e8 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -150,12 +150,14 @@ def _get_properties(self): buf = self._read_bytes(const.dataset_offset, const.dataset_length) self.name = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.name = self.name.decode(self.encoding or self.default_encoding) + self.name = self.name.decode( + self.encoding or self.default_encoding) buf = self._read_bytes(const.file_type_offset, const.file_type_length) self.file_type = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.file_type = self.file_type.decode(self.encoding or self.default_encoding) + self.file_type = self.file_type.decode( + self.encoding or self.default_encoding) # Timestamp is epoch 01/01/1960 epoch = pd.datetime(1960, 1, 1) @@ -184,19 +186,22 @@ def _get_properties(self): const.sas_release_length) self.sas_release = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.sas_release = self.sas_release.decode(self.encoding or self.default_encoding) + self.sas_release = self.sas_release.decode( + self.encoding or self.default_encoding) buf = self._read_bytes(const.sas_server_type_offset + total_align, const.sas_server_type_length) self.server_type = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.server_type = self.server_type.decode(self.encoding or self.default_encoding) + self.server_type = self.server_type.decode( + self.encoding or self.default_encoding) buf = self._read_bytes(const.os_version_number_offset + total_align, const.os_version_number_length) self.os_version = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.os_version = self.os_version.decode(self.encoding or self.default_encoding) + self.os_version = self.os_version.decode( + self.encoding or self.default_encoding) buf = self._read_bytes(const.os_name_offset + total_align, const.os_name_length) @@ -208,7 +213,8 @@ def _get_properties(self): const.os_maker_length) self.os_name = buf.rstrip(b'\x00 ') if self.convert_header_text: - self.os_name = self.os_name.decode(self.encoding or self.default_encoding) + self.os_name = self.os_name.decode( + self.encoding or self.default_encoding) # Read a single float of the given width (4 or 8). def _read_float(self, offset, width): @@ -442,7 +448,8 @@ def _process_columntext_subheader(self, offset, length): self.creator_proc = buf[0:self._lcp] if self.convert_header_text: if hasattr(self, "creator_proc"): - self.creator_proc = self.creator_proc.decode(self.encoding or self.default_encoding) + self.creator_proc = self.creator_proc.decode( + self.encoding or self.default_encoding) def _process_columnname_subheader(self, offset, length): int_len = self._int_length @@ -628,7 +635,8 @@ def _chunk_to_dataframe(self): elif self.column_types[j] == b's': rslt[name] = self._string_chunk[js, :] if self.convert_text and (self.encoding is not None): - rslt[name] = rslt[name].str.decode(self.encoding or self.default_encoding) + rslt[name] = rslt[name].str.decode( + self.encoding or self.default_encoding) if self.blank_missing: ii = rslt[name].str.len() == 0 rslt.loc[ii, name] = np.nan diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index 4fcf186ae58bb..a7b7bf8855343 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -79,7 +79,7 @@ def test_encoding_options(): from pandas.io.sas.sas7bdat import SAS7BDATReader rdr = SAS7BDATReader(fname, convert_header_text=False) df3 = rdr.read() - for x,y in zip(df1.columns, df3.columns): + for x, y in zip(df1.columns, df3.columns): assert(x == y.decode()) From ea87a7fe88a56dae263ae2951206f00c3236c701 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Thu, 21 Apr 2016 11:10:51 -0400 Subject: [PATCH 15/19] Fix encoding handling bug for py2 --- pandas/io/sas/sas7bdat.py | 5 +++-- pandas/io/sas/sas_constants.py | 4 ++-- pandas/io/tests/sas/test_sas7bdat.py | 11 ++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 1dcedd523f6e8..c6469e0a95a62 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -406,7 +406,8 @@ def _process_columntext_subheader(self, offset, length): text_block_size = self._read_int(offset, const.text_block_size_length) buf = self._read_bytes(offset, text_block_size) - cname = buf[0:text_block_size].rstrip(b"\x00 ") + cname_raw = buf[0:text_block_size].rstrip(b"\x00 ") + cname = cname_raw if self.convert_header_text: cname = cname.decode(self.encoding or self.default_encoding) self.column_names_strings.append(cname) @@ -415,7 +416,7 @@ def _process_columntext_subheader(self, offset, length): column_name = self.column_names_strings[0] compression_literal = "" for cl in const.compression_literals: - if cl in str(column_name): + if cl in cname_raw: compression_literal = cl self.compression = compression_literal offset -= self._int_length diff --git a/pandas/io/sas/sas_constants.py b/pandas/io/sas/sas_constants.py index 62e1b112476ba..471196b6291b6 100644 --- a/pandas/io/sas/sas_constants.py +++ b/pandas/io/sas/sas_constants.py @@ -91,8 +91,8 @@ column_label_offset_length = 2 column_label_length_offset = 32 column_label_length_length = 2 -rle_compression = 'SASYZCRL' -rdc_compression = 'SASYZCR2' +rle_compression = b'SASYZCRL' +rdc_compression = b'SASYZCR2' compression_literals = [rle_compression, rdc_compression] diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index a7b7bf8855343..b1d71e46a214c 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -97,8 +97,17 @@ def test_productsales(): def test_12659(): dirpath = tm.get_data_path() fname = os.path.join(dirpath, "test_12659.sas7bdat") - df = pd.read_sas(fname, encoding='latin1') + df = pd.read_sas(fname) fname = os.path.join(dirpath, "test_12659.csv") df0 = pd.read_csv(fname) df0 = df0.astype(np.float64) tm.assert_frame_equal(df, df0) + +def test_airline(): + dirpath = tm.get_data_path() + fname = os.path.join(dirpath, "airline.sas7bdat") + df = pd.read_sas(fname) + fname = os.path.join(dirpath, "airline.csv") + df0 = pd.read_csv(fname) + df0 = df0.astype(np.float64) + tm.assert_frame_equal(df, df0, check_exact=False) From b7de358a68cbd2b3504c31fa57838c65bfb0699b Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Thu, 21 Apr 2016 13:23:02 -0400 Subject: [PATCH 16/19] flake8 fixes --- doc/source/whatsnew/v0.18.1.txt | 2 +- pandas/io/sas/sas7bdat.py | 1 - pandas/io/sas/sas_constants.py | 1 + pandas/io/tests/sas/test_sas7bdat.py | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index abb193f0eb331..d70f3807d264d 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -248,7 +248,7 @@ Deprecations Performance Improvements ~~~~~~~~~~~~~~~~~~~~~~~~ -- Improved speed of SAS reader (PR 12656) +- Improved speed of SAS reader (:issue:`12656`) - Improved performance of ``DataFrame.to_sql`` when checking case sensitivity for tables. Now only checks if table has been created correctly when table name is not lower case. (:issue:`12876`) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index c6469e0a95a62..7d8390a73af7e 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -413,7 +413,6 @@ def _process_columntext_subheader(self, offset, length): self.column_names_strings.append(cname) if len(self.column_names_strings) == 1: - column_name = self.column_names_strings[0] compression_literal = "" for cl in const.compression_literals: if cl in cname_raw: diff --git a/pandas/io/sas/sas_constants.py b/pandas/io/sas/sas_constants.py index 471196b6291b6..65ae1e9102cb2 100644 --- a/pandas/io/sas/sas_constants.py +++ b/pandas/io/sas/sas_constants.py @@ -101,6 +101,7 @@ encoding_names = {29: "latin1", 20: "utf-8", 33: "cyrillic", 60: "wlatin2", 61: "wcyrillic", 62: "wlatin1", 90: "ebcdic870"} + class index: rowSizeIndex = 0 columnSizeIndex = 1 diff --git a/pandas/io/tests/sas/test_sas7bdat.py b/pandas/io/tests/sas/test_sas7bdat.py index b1d71e46a214c..6661d9fee5df0 100644 --- a/pandas/io/tests/sas/test_sas7bdat.py +++ b/pandas/io/tests/sas/test_sas7bdat.py @@ -103,6 +103,7 @@ def test_12659(): df0 = df0.astype(np.float64) tm.assert_frame_equal(df, df0) + def test_airline(): dirpath = tm.get_data_path() fname = os.path.join(dirpath, "airline.sas7bdat") From fe4731bb89fd6a90c5a021228919bf45a38aa714 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Thu, 21 Apr 2016 21:42:46 -0400 Subject: [PATCH 17/19] Integrate jreback's cython improvements --- pandas/io/sas/sas7bdat.py | 5 +- pandas/io/sas/saslib.pyx | 343 ++++++++++++++++++++++---------------- 2 files changed, 204 insertions(+), 144 deletions(-) diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index 7d8390a73af7e..b75f05cf9ed7e 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -20,7 +20,7 @@ import numpy as np import struct import pandas.io.sas.sas_constants as const -from pandas.io.sas.saslib import do_read +from pandas.io.sas.saslib import Parser class _subheader_pointer(object): @@ -583,7 +583,8 @@ def read(self, nrows=None): self._byte_chunk = np.empty((nd, 8 * nrows), dtype=np.uint8) self._current_row_in_chunk_index = 0 - do_read(self, nrows) + p = Parser(self) + p.read(nrows) rslt = self._chunk_to_dataframe() if self.index is not None: diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 989a733cece6e..7ab07484b84bf 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -7,16 +7,12 @@ import sas_constants as const # algorithm. It is partially documented here: # # https://cran.r-project.org/web/packages/sas7bdat/vignettes/sas7bdat.pdf -cdef rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): +cdef np.ndarray[uint8_t, ndim=1] rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - cdef uint8_t control_byte - cdef uint8_t [:] result = np.zeros(result_length, np.uint8) - cdef int rpos = 0 - cdef int ipos = 0 - cdef int i - cdef int nbytes - cdef uint8_t x - cdef length = len(inbuff) + cdef: + uint8_t control_byte, x, end_of_first_byte + uint8_t [:] result = np.zeros(result_length, np.uint8) + int rpos = 0, ipos = 0, i, nbytes, length = len(inbuff) while ipos < length: control_byte = inbuff[ipos] & 0xF0 @@ -111,17 +107,13 @@ cdef rle_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): # rdc_decompress decompresses data using the Ross Data Compression algorithm: # # http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/CUJ/1992/9210/ross/ross.htm -cdef rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): - - cdef uint8_t cmd - cdef uint16_t ctrl_bits - cdef uint16_t ctrl_mask = 0 - cdef uint16_t ofs - cdef uint16_t cnt - cdef int ipos = 0 - cdef int rpos = 0 - cdef int k - cdef uint8_t [:] outbuff = np.zeros(result_length, dtype=np.uint8) +cdef np.ndarray[uint8_t, ndim=1] rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): + + cdef: + uint8_t cmd + uint16_t ctrl_bits, ctrl_mask = 0, ofs, cnt + int ipos = 0, rpos = 0, k + uint8_t [:] outbuff = np.zeros(result_length, dtype=np.uint8) ii = -1 @@ -190,136 +182,203 @@ cdef rdc_decompress(int result_length, np.ndarray[uint8_t, ndim=1] inbuff): return np.asarray(outbuff) -cdef decompress(object parser, int row_length, page): - page = np.frombuffer(page, dtype=np.uint8) - if parser.compression == const.rle_compression: - return rle_decompress(row_length, page) - elif parser.compression == const.rdc_compression: - return rdc_decompress(row_length, page) - else: - raise ValueError("unknown SAS compression method: %s" % - parser.compression) +cdef class Parser(object): -def do_read(object parser, int nrows): - cdef int i + cdef: + int column_count + long[:] lengths + long[:] offsets + long[:] column_types + uint8_t[:, :] byte_chunk + object[:, :] string_chunk + char *cached_page + int current_row_on_page_index + int current_row_in_chunk_index + int current_row_in_file_index + int row_length + int bit_offset + int subheader_pointer_length + bint is_little_endian + np.ndarray[uint8_t, ndim=1] (*decompress)(int result_length, np.ndarray[uint8_t, ndim=1] inbuff) + object parser + + def __init__(self, object parser): + cdef: + int j + char[:] column_types + + self.current_row_on_page_index = parser._current_row_on_page_index + self.current_row_in_chunk_index = parser._current_row_in_chunk_index + self.current_row_in_file_index = parser._current_row_in_file_index + self.parser = parser + self.column_count = parser.column_count + self.lengths = parser._column_data_lengths + self.offsets = parser._column_data_offsets + self.byte_chunk = parser._byte_chunk + self.string_chunk = parser._string_chunk + self.row_length = parser.row_length + self.cached_page = parser._cached_page + self.bit_offset = self.parser._page_bit_offset + self.subheader_pointer_length = self.parser._subheader_pointer_length + self.is_little_endian = parser.byte_order == "<" + self.column_types = np.empty(self.column_count, dtype=long) + + column_types = parser.column_types + + # map column types + for j in range(self.column_count): + if column_types[j] == b'd': + self.column_types[j] = 1 + elif column_types[j] == b's': + self.column_types[j] = 2 + else: + raise ValueError("unknown column type: %s" % self.parser.columns[j].ctype) - for i in range(nrows): - done = readline(parser) - if done: - break + # compression + if parser.compression == const.rle_compression: + self.decompress = rle_decompress + elif parser.compression == const.rdc_compression: + self.decompress = rdc_decompress + else: + self.decompress = NULL + def read(self, int nrows): + cdef: + bint done + int i -cdef readline(object parser): + for i in range(nrows): + done = self.readline() + if done: + break - cdef: - int offset, bit_offset, align_correction, subheader_pointer_length + # update the parser + self.parser._current_row_on_page_index = self.current_row_on_page_index + self.parser._current_row_in_chunk_index = self.current_row_in_chunk_index + self.parser._current_row_in_file_index = self.current_row_in_file_index - bit_offset = parser._page_bit_offset - subheader_pointer_length = parser._subheader_pointer_length + cdef bint read_next_page(self): + cdef done - # If there is no page, go to the end of the header and read a page. - if parser._cached_page is None: - parser._path_or_buf.seek(parser.header_length) - done = parser._read_next_page() + done = self.parser._read_next_page() if done: - return True - - # Loop until a data row is read - while True: - if parser._current_page_type == const.page_meta_type: - flag = (parser._current_row_on_page_index >= - len(parser._current_page_data_subheader_pointers)) - if flag: - done = parser._read_next_page() - if done: - return True - parser._current_row_on_page_index = 0 - continue - current_subheader_pointer = ( - parser._current_page_data_subheader_pointers[ - parser._current_row_on_page_index]) - process_byte_array_with_data(parser, - current_subheader_pointer.offset, - current_subheader_pointer.length) - return False - elif parser._current_page_type in const.page_mix_types: - align_correction = (bit_offset + const.subheader_pointers_offset + - parser._current_page_subheaders_count * - subheader_pointer_length) - align_correction = align_correction % 8 - offset = bit_offset + align_correction - offset += const.subheader_pointers_offset - offset += (parser._current_page_subheaders_count * - subheader_pointer_length) - offset += parser._current_row_on_page_index * parser.row_length - process_byte_array_with_data(parser, offset, parser.row_length) - mn = min(parser.row_count, parser._mix_page_row_count) - if parser._current_row_on_page_index == mn: - done = parser._read_next_page() - if done: - return True - parser._current_row_on_page_index = 0 - return False - elif parser._current_page_type == const.page_data_type: - process_byte_array_with_data(parser, - bit_offset + - const.subheader_pointers_offset + - parser._current_row_on_page_index * - parser.row_length, - parser.row_length) - flag = (parser._current_row_on_page_index == - parser._current_page_block_count) - if flag: - done = parser._read_next_page() - if done: - return True - parser._current_row_on_page_index = 0 - return False + self.cached_page = NULL else: - raise ValueError("unknown page type: %s", - parser._current_page_type) - - -cdef process_byte_array_with_data(object parser, int offset, int length): - - cdef: - int s, j, k, m, start, jb, js, lngt - long[:] lengths = parser._column_data_lengths - long[:] offsets = parser._column_data_offsets - char[:] column_types = parser.column_types - uint8_t[:, :] byte_chunk = parser._byte_chunk - object[:, :] string_chunk = parser._string_chunk - np.ndarray[uint8_t, ndim=1] source - np.ndarray[uint8_t, ndim=1] raw_source = np.frombuffer(parser._cached_page[offset:offset+length], dtype=np.uint8) - - if (parser.compression != "") and (length < parser.row_length): - source = decompress(parser, parser.row_length, raw_source) - else: - source = raw_source - - s = 8 * parser._current_row_in_chunk_index - js = 0 - jb = 0 - for j in range(parser.column_count): - lngt = lengths[j] - if lngt == 0: - break - start = offsets[j] - if column_types[j] == b'd': - if parser.byte_order == "<": - m = s + 8 - lngt + self.cached_page = self.parser._cached_page + self.current_row_on_page_index = 0 + return done + + cdef bint readline(self): + + cdef: + int offset, bit_offset, align_correction, subheader_pointer_length + bint done + + bit_offset = self.bit_offset + subheader_pointer_length = self.subheader_pointer_length + + # If there is no page, go to the end of the header and read a page. + if self.cached_page == NULL: + self.parser._path_or_buf.seek(self.parser.header_length) + done = self.read_next_page() + if done: + return True + + # Loop until a data row is read + while True: + if self.parser._current_page_type == const.page_meta_type: + flag = (self.current_row_on_page_index >= + len(self.parser._current_page_data_subheader_pointers)) + if flag: + done = self.read_next_page() + if done: + return True + continue + current_subheader_pointer = ( + self.parser._current_page_data_subheader_pointers[ + self.current_row_on_page_index]) + self.process_byte_array_with_data(current_subheader_pointer.offset, + current_subheader_pointer.length) + return False + elif self.parser._current_page_type in const.page_mix_types: + align_correction = (bit_offset + const.subheader_pointers_offset + + self.parser._current_page_subheaders_count * + subheader_pointer_length) + align_correction = align_correction % 8 + offset = bit_offset + align_correction + offset += const.subheader_pointers_offset + offset += (self.parser._current_page_subheaders_count * + subheader_pointer_length) + offset += self.current_row_on_page_index * self.row_length + self.process_byte_array_with_data(offset, + self.row_length) + mn = min(self.parser.row_count, self.parser._mix_page_row_count) + if self.current_row_on_page_index == mn: + done = self.read_next_page() + if done: + return True + return False + elif self.parser._current_page_type == const.page_data_type: + self.process_byte_array_with_data(bit_offset + + const.subheader_pointers_offset + + self.current_row_on_page_index * + self.row_length, + self.row_length) + flag = (self.current_row_on_page_index == + self.parser._current_page_block_count) + if flag: + done = self.read_next_page() + if done: + return True + return False else: - m = s - for k in range(lngt): - byte_chunk[jb, m + k] = source[start + k] - jb += 1 - elif column_types[j] == b's': - string_chunk[js, parser._current_row_in_chunk_index] = source[start:(start+lngt)].tostring().rstrip() - js += 1 - else: - raise ValueError("unknown column type: %s" % parser.columns[j].ctype) - - parser._current_row_on_page_index += 1 - parser._current_row_in_chunk_index += 1 - parser._current_row_in_file_index += 1 + raise ValueError("unknown page type: %s", + self.parser._current_page_type) + + cdef void process_byte_array_with_data(self, int offset, int length): + + cdef: + long s, j, k, m, jb, js, lngt, start + np.ndarray[uint8_t, ndim=1] source + long[:] column_types + long[:] lengths + long[:] offsets + uint8_t[:, :] byte_chunk + object[:, :] string_chunk + + source = np.frombuffer(self.cached_page[offset:offset+length], dtype=np.uint8) + + if self.decompress != NULL and (length < self.row_length): + source = self.decompress(self.row_length, source) + + column_types = self.column_types + lengths = self.lengths + offsets = self.offsets + byte_chunk = self.byte_chunk + string_chunk = self.string_chunk + s = 8 * self.current_row_in_chunk_index + js = 0 + jb = 0 + for j in range(self.column_count): + lngt = lengths[j] + if lngt == 0: + break + start = offsets[j] + if column_types[j] == 1: + # decimal + if self.is_little_endian: + m = s + 8 - lngt + else: + m = s + for k in range(lngt): + byte_chunk[jb, m + k] = source[start + k] + jb += 1 + elif column_types[j] == 2: + # string + string_chunk[js, self.current_row_in_chunk_index] = source[start:(start+lngt)].tostring().rstrip() + js += 1 + + self.current_row_on_page_index += 1 + self.current_row_in_chunk_index += 1 + self.current_row_in_file_index += 1 From af085f7d330cd6040fa7d37282da644c2e1acfb3 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Thu, 21 Apr 2016 22:02:21 -0400 Subject: [PATCH 18/19] Add one more type --- pandas/io/sas/saslib.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/sas/saslib.pyx b/pandas/io/sas/saslib.pyx index 7ab07484b84bf..6bd72a56f4743 100644 --- a/pandas/io/sas/saslib.pyx +++ b/pandas/io/sas/saslib.pyx @@ -273,7 +273,7 @@ cdef class Parser(object): cdef: int offset, bit_offset, align_correction, subheader_pointer_length - bint done + bint done, flag bit_offset = self.bit_offset subheader_pointer_length = self.subheader_pointer_length From b3024ed5a60b464c1f9ea165e1d857f830530b17 Mon Sep 17 00:00:00 2001 From: Kerby Shedden Date: Fri, 22 Apr 2016 10:56:38 -0400 Subject: [PATCH 19/19] Add missing test data files --- pandas/io/tests/sas/data/airline.csv | 33 ++++++++++++++++++++++ pandas/io/tests/sas/data/airline.sas7bdat | Bin 0 -> 5120 bytes 2 files changed, 33 insertions(+) create mode 100644 pandas/io/tests/sas/data/airline.csv create mode 100644 pandas/io/tests/sas/data/airline.sas7bdat diff --git a/pandas/io/tests/sas/data/airline.csv b/pandas/io/tests/sas/data/airline.csv new file mode 100644 index 0000000000000..a6336446003a9 --- /dev/null +++ b/pandas/io/tests/sas/data/airline.csv @@ -0,0 +1,33 @@ +YEAR,Y,W,R,L,K +1948.00000839,1.21399998665,0.243000000715,0.145400002599,1.41499996185,0.611999988556 +1949.0000084,1.35399997234,0.259999990463,0.218099996448,1.38399994373,0.559000015259 +1950.0,1.56900000572,0.277999997139,0.315699994564,1.38800001144,0.573000013828 +1951.0,1.94799995422,0.29699999094,0.393999993801,1.54999995232,0.56400001049 +1952.0,2.2650001049,0.310000002384,0.355899989605,1.80200004578,0.574000000954 +1953.0,2.73099994659,0.321999996901,0.359299987555,1.92599999905,0.711000025272 +1954.0,3.02500009537,0.335000008345,0.402500003576,1.96399998665,0.776000022888 +1955.0,3.56200003624,0.34999999404,0.396100014448,2.11599993706,0.827000021935 +1956.0,3.97900009155,0.361000001431,0.38220000267,2.43499994278,0.800000011921 +1957.00036436,4.42000007629,0.379000008106,0.30450001359,2.70700001717,0.921000003815 +1958.00096758,4.56300020218,0.391000002623,0.328399986029,2.70600008965,1.06700003147 +1959.00096116,5.38500022888,0.425999999046,0.38560000062,2.84599995613,1.08299994469 +1960.0,5.55399990082,0.441000014544,0.319299995899,3.08899998665,1.48099994659 +1961.0,5.46500015259,0.460000008345,0.307900011539,3.12199997902,1.73599994183 +1962.0,5.82499980927,0.485000014305,0.378300011158,3.18400001526,1.92599999905 +1963.0,6.87599992752,0.505999982357,0.418000012636,3.26300001144,2.04099988937 +1964.0,7.82299995422,0.537999987602,0.516300022602,3.41199994087,1.99699997902 +1965.0,9.11999988556,0.56400001049,0.587899982929,3.62299990654,2.25699996948 +1966.00082638,10.5120000839,0.586000025272,0.536899983883,4.07399988174,2.742000103 +1967.00001699,13.0200004578,0.621999979019,0.444299995899,4.71000003815,3.56399989128 +1968.00096202,15.2609996796,0.666000008583,0.305200010538,5.21700000763,4.76700019836 +1969.00023643,16.3129997253,0.731000006199,0.233199998736,5.5689997673,6.5110001564 +1970.00003926,16.0020008087,0.830999970436,0.188299998641,5.49499988556,7.62699985504 +1971.00096131,15.8760004044,0.90600001812,0.202299997211,5.33400011063,8.67300033569 +1972.0001291,16.6620006561,1.0,0.250600010157,5.34499979019,8.33100032806 +1973.00001162,17.013999939,1.05599999428,0.266799986362,5.66200017929,8.55700016022 +1974.00096128,19.3050003052,1.13100004196,0.266400009394,5.72900009155,9.50800037384 +1975.0,18.7210006714,1.24699997902,0.230100005865,5.72200012207,9.06200027466 +1976.0,19.25,1.375,0.345200002193,5.76200008392,8.26200008392 +1977.0,20.6469993591,1.54400002956,0.450800001621,5.87699985504,7.47399997711 +1978.0,22.7259998322,1.70299994946,0.587700009346,6.10799980164,7.10400009155 +1979.0,23.6189994812,1.77900004387,0.534600019455,6.85200023651,6.87400007248 diff --git a/pandas/io/tests/sas/data/airline.sas7bdat b/pandas/io/tests/sas/data/airline.sas7bdat new file mode 100644 index 0000000000000000000000000000000000000000..462299bc6a952a38e360592c3f10799dad8481a0 GIT binary patch literal 5120 zcmeI04@^~69LIk=o+S8EP*R5fZbYqrAmSfnlil;Cm;{kiC)!-Q&w3_C@WQ8Jb9#|1 zD_xc`bDCaLhOWO;=WH`~bEa09v`{29uc2v7r^ONx<@Njh&VBgs&qTMj+W2jc-|u|S z=lssOzjMypeE?jy-3_JM*4UVXU%%fKBKHkm8aFUHCLsY*6T%#+&NeJOteu`3h6q}8 zcvujb;GUq2$;!!eXN}Lv%FR^n)keD~x@jzcSw3^uVfS^9GbuX-&qtmX7Y)$10Boiw zr=UHPl9!ds*zNw@sh{S|$^GJD@N;9HnOS*BH)@^j&Iho6XVf#yr~<&R6F)#Jd|QI5fZyX;v-SlnT;W_?}nIi zwB2#vT0tq_DQHyDo9p5ak~ikA*-aj5H(X+0T3)Y$pINJA^WSz~HF<4o!w=+@l`od?p*{c{>r zSk5et>%rQ(_*!0P)9fR>&Xcc&lWwe9^@j={$o0m49z&XV%gu~&em*~=aiNl*0}hp( zSK&iul`_$jSSv9?&Ry!1>#nb0)+p@xrwVIi-i)^&AXYCQb(H2dKKV@MU%e-SbhBv! zvIetec3N-Jl*#GGc%Au0TstFkJJ$!2wlt}*ky&``80WP%_>MFC@itzsaW z3Y%o!s+>NQSI>w#!EDTMUIRW;BT+CyPz1iJJ7uy?g|F$t}_%aemGxmvGI*uwP?I*Js4^60k?I%D5ELl;>VN!|PmFcYwTAIfnPh7}YIa zz z=e&sV_l)^XAHJiw=Y0OG#5Zugrj!8kS~?iUg#hQt`MVd}t$U<3Nub0&g(*B)W}v9_OT`vTj` zf!8p{a{Ljl+j;Ve^ZkBrp)zfrg8vQxn3CxpKhNtao`U@a-Z|a^rD(cRG~4f+?T0+n z=6YW8D#aeZ7vu5x$uIPKi@me_K`n>Id8QQ=EBS>qCL7x?czpSOPl2t--v-gB-(*<4 z6cnju_ubTad%^8X|OK~#&u{S}%5Fde!