处理具有重复多值特征的数据集
Dealing with datasets with repeated multivalued features
我们有一个 Dataset 是稀疏表示,有 25 个特征和 1 个二进制标签。比如一行数据集是:
Label: 0
exid: 24924687
Features:
11:0 12:1 13:0 14:6 15:0 17:2 17:2 17:2 17:2 17:2 17:2
21:11 21:42 21:42 21:42 21:42 21:42
22:35 22:76 22:27 22:28 22:25 22:15 24:1888
25:9 33:322 33:452 33:452 33:452 33:452 33:452 35:14
所以,有时特征有多个值,它们可以相同也可以不同,网站上说:
Some categorical features are multi-valued (order does not matter)
我们不知道特征的语义是什么以及分配给它们的值(由于某些隐私问题,它们被隐藏到 public)
我们只知道:
Label
表示用户是否点击了推荐的广告。
Features
描述的是推荐给用户的产品。
Task
是在给定产品广告的情况下,预测用户点击的概率。
欢迎对以下问题提出意见:
- 将此类数据集导入 Python 数据结构的最佳方法是什么。
- 如何处理多值特征,特别是当它们具有相似值重复
k
次时?
这是一个非常笼统的问题,但据我所知,如果您想使用某些 ML 方法,首先将数据转换为 tidy data format 是明智的。
据我无法从 documentation 中看出 @RootTwo 在他的评论中很好地引用了,你实际上正在处理 两个 数据集:一个示例平面 table 和一个产品持平 table。 (如果需要,您可以稍后加入两者以获得一个 table。)
让我们首先创建一些解析器,将不同的行解码为具有一定信息量的数据结构:
对于带有示例的行,我们可以使用:
def process_example(example_line):
# example ${exID}: ${hashID} ${wasAdClicked} ${propensity} ${nbSlots} ${nbCandidates} ${displayFeat1}:${v_1}
# 0 1 2 3 4 5 6 7 ...
feature_names = ['ex_id', 'hash', 'clicked', 'propensity', 'slots', 'candidates'] + \
['display_feature_' + str(i) for i in range(1, 11)]
are_numbers = [1, 3, 4, 5, 6]
parts = example_line.split(' ')
parts[1] = parts[1].replace(':', '')
for i in are_numbers:
parts[i] = float(parts[i])
if parts[i].is_integer():
parts[i] = int(parts[i])
featues = [int(ft.split(':')[1]) for ft in parts[7:]]
return dict(zip(feature_names, parts[1:7] + featues))
这种方法有点笨拙,但可以完成工作:解析特征并尽可能转换为数字。输出看起来像:
{'ex_id': 20184824,
'hash': '57548fae76b0aa2f2e0d96c40ac6ae3057548faee00912d106fc65fc1fa92d68',
'clicked': 0,
'propensity': 1.416489e-07,
'slots': 6,
'candidates': 30,
'display_feature_1': 728,
'display_feature_2': 90,
'display_feature_3': 1,
'display_feature_4': 10,
'display_feature_5': 16,
'display_feature_6': 1,
'display_feature_7': 26,
'display_feature_8': 11,
'display_feature_9': 597,
'display_feature_10': 7}
接下来是产品示例。正如您提到的,问题是值的多次出现。我认为按频率聚合独特的 feature-value 对是明智的。信息不会丢失,但它可以帮助我们编码整洁的样本。那应该解决你的第二个问题。
import toolz # pip install toolz
def process_product(product_line):
# ${wasProduct1Clicked} exid:${exID} ${productFeat1_1}:${v1_1} ...
parts = product_line.split(' ')
meta = {'label': int(parts[0]),
'ex_id': int(parts[1].split(':')[1])}
# extract feautes that are ${productFeat1_1}:${v1_1} separated by ':' into a dictionary
features = [('product_feature_' + str(i), int(v))
for i, v in map(lambda x: x.split(':'), parts[2:])]
# count each unique value and transform them into
# feature_name X feature_value X feature_frequency
products = [dict(zip(['feature', 'value', 'frequency'], (*k, v)))
for k, v in toolz.countby(toolz.identity, features).items()]
# now merge the meta information into each product
return [dict(p, **meta) for p in products]
基本上提取每个示例的标签和特征(第 40 行的示例):
[{'feature': 'product_feature_11',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_12',
'value': 1,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_13',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_14',
'value': 2,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_15',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_17',
'value': 2,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_21',
'value': 55,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_22',
'value': 14,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_22',
'value': 54,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_24',
'value': 3039,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_25',
'value': 721,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_33',
'value': 386,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_35',
'value': 963,
'frequency': 1,
'label': 0,
'ex_id': 19168103}]
因此,当您逐行处理流时,您可以决定是映射示例还是映射产品:
def process_stream(stream):
for content in stream:
if 'example' in content:
yield process_example(content)
else:
yield process_product(content)
我决定在这里做一个生成器,因为如果您决定不使用 pandas
,它将有利于以功能方式处理数据。否则列表压缩将
做你的炸鸡。
现在是有趣的部分:我们逐行阅读给定(示例)url 中的行,然后
将它们分配到相应的数据集(示例或产品)中。
我将在这里使用 reduce
,因为它很有趣 :-)。我不会详细说明 map/reduce
实际做了什么(这取决于您)。您始终可以改用简单的 for 循环。
import urllib.request
import toolz # pip install toolz
lines_stream = (line.decode("utf-8").strip()
for line in urllib.request.urlopen('http://www.cs.cornell.edu/~adith/Criteo/sample.txt'))
# if you care about concise but hacky approach you could do:
# blubb = list(toolz.partitionby(lambda x: 'hash' in x, process_file(lines_stream)))
# examples_only = blubb[slice(0, len(blubb), 2)]
# products_only = blubb[slice(1, len(blubb), 2)]
# but to introduce some functional approach lets implement a reducer
def dataset_reducer(datasets, content):
which_one = 0 if 'hash' in content else 1
datasets[which_one].append(content)
return datasets
# and process the stream using the reducer. Which results in two datasets:
examples_dataset, product_dataset = toolz.reduce(dataset_reducer, process_stream(lines), [[], []])
从这里您可以将您的数据集转换成一个整洁的数据框,您可以使用它来应用机器学习。当心 NaN
/缺失值、分布等。您可以将两个数据集与 merge
连接起来,以获得一大块 table 样本 X 特征。然后你将或多或少能够使用不同的方法,例如scikit-learn
.
import pandas
examples_dataset = pandas.DataFrame(examples_dataset)
product_dataset = pandas.concat(pandas.DataFrame(p) for p in product_dataset)
示例数据集
candidates clicked ... propensity slots
0 30 0 ... 1.416489e-07 6
1 23 0 ... 5.344958e-01 3
2 23 1 ... 1.774762e-04 3
3 28 0 ... 1.158855e-04 6
产品数据集(product_dataset.sample(10)
)
ex_id feature frequency label value
6 10244535 product_feature_21 1 0 10
9 37375474 product_feature_25 1 0 4
6 44432959 product_feature_25 1 0 263
15 62131356 product_feature_35 1 0 14
8 50383824 product_feature_24 1 0 228
8 63624159 product_feature_20 1 0 30
3 99375433 product_feature_14 1 0 0
9 3389658 product_feature_25 1 0 43
20 59461725 product_feature_31 8 0 4
11 17247719 product_feature_21 3 0 5
注意 product_dataset
。您可以 'pivot' 将行中的要素作为列(参见 reshaping docs)。
示例文件每个示例都有一些有趣的功能。在字典中展开,每个示例看起来像:
{'ex_id': int,
'hash': str,
'clicked': bool,
'propensity': float,
'slots': int,
'candidates': int,
'display_feature_1': [int],
'display_feature_2': [int],
'display_feature_3': [int],
'display_feature_4': [int],
'display_feature_5': [int],
'display_feature_6': [int],
'display_feature_7': [int],
'display_feature_8': [int],
'display_feature_9': [int],
'display_feature_10': [int],
'display_feature_11': [int],
'display_feature_12': [int],
'display_feature_13': [int],
'display_feature_14': [int],
'display_feature_15': [int],
'display_feature_16': [int],
'display_feature_17': [int],
'display_feature_18': [int],
'display_feature_19': [int],
'display_feature_20': [int],
'display_feature_21': [int],
'display_feature_22': [int],
'display_feature_23': [int],
'display_feature_24': [int],
'display_feature_25': [int],
'display_feature_26': [int],
'display_feature_27': [int],
'display_feature_28': [int],
'display_feature_29': [int],
'display_feature_30': [int],
'display_feature_31': [int],
'display_feature_32': [int],
'display_feature_33': [int],
'display_feature_34': [int],
'display_feature_35': [int]
}
其中功能 1-35 可能存在也可能不存在,并且可能重复也可能不重复。对于这种大小的数据集,合理的做法是将其存储为 tuple
的 list
,因此每个 tuple
对应一个示例 ID,如下所示:
(
int, # exid
str, # hash
bool, # clicked
float, # propensity
int, # slots
int, # candidates
dict # the display features
)
适合 35 个显示特征的 dict
结构是
{k+1 : [] for k in range(35)}
总的来说,这个示例数据结构可以概括为一个元组列表,每个元组中的最后一个元素是一个字典。
假设您在本地有 sample.txt
,您可以像这样填充此结构:
examples = []
with open('sample.txt', 'r') as fp:
for line in fp:
line = line.strip('\n')
if line[:7] == 'example':
items = line.split(' ')
items = [item.strip(':') for item in items]
examples.append((
int(items[1]), # exid
items[2], # hash
bool(items[3]), # clicked
float(items[4]), # propensity
int(items[5]), # slots
int(items[6]), # candidates
{k+1 : [] for k in range(35)} # the display features
))
for k in range(10):
examples[-1][6][k+1].append(int(items[k+7].split(':')[1]))
else:
items = line.split(' ')
while len(items) > 2:
keyval = items.pop()
key = int(keyval.split(':')[0])
val = int(keyval.split(':')[1])
examples[-1][6][key].append(val)
记录的这种数据结构可以转换为JSON,并读取到一个numpy数组。您可以轻松地根据每个元组中的任何元素对其进行排序,并快速迭代它。
处理多值记录项的方法是将它们存储在列表字典中。这使得收集他们的统计数据变得容易。
我们有一个 Dataset 是稀疏表示,有 25 个特征和 1 个二进制标签。比如一行数据集是:
Label: 0
exid: 24924687
Features:
11:0 12:1 13:0 14:6 15:0 17:2 17:2 17:2 17:2 17:2 17:2
21:11 21:42 21:42 21:42 21:42 21:42
22:35 22:76 22:27 22:28 22:25 22:15 24:1888
25:9 33:322 33:452 33:452 33:452 33:452 33:452 35:14
所以,有时特征有多个值,它们可以相同也可以不同,网站上说:
Some categorical features are multi-valued (order does not matter)
我们不知道特征的语义是什么以及分配给它们的值(由于某些隐私问题,它们被隐藏到 public)
我们只知道:
Label
表示用户是否点击了推荐的广告。Features
描述的是推荐给用户的产品。Task
是在给定产品广告的情况下,预测用户点击的概率。
欢迎对以下问题提出意见:
- 将此类数据集导入 Python 数据结构的最佳方法是什么。
- 如何处理多值特征,特别是当它们具有相似值重复
k
次时?
这是一个非常笼统的问题,但据我所知,如果您想使用某些 ML 方法,首先将数据转换为 tidy data format 是明智的。
据我无法从 documentation 中看出 @RootTwo 在他的评论中很好地引用了,你实际上正在处理 两个 数据集:一个示例平面 table 和一个产品持平 table。 (如果需要,您可以稍后加入两者以获得一个 table。)
让我们首先创建一些解析器,将不同的行解码为具有一定信息量的数据结构:
对于带有示例的行,我们可以使用:
def process_example(example_line):
# example ${exID}: ${hashID} ${wasAdClicked} ${propensity} ${nbSlots} ${nbCandidates} ${displayFeat1}:${v_1}
# 0 1 2 3 4 5 6 7 ...
feature_names = ['ex_id', 'hash', 'clicked', 'propensity', 'slots', 'candidates'] + \
['display_feature_' + str(i) for i in range(1, 11)]
are_numbers = [1, 3, 4, 5, 6]
parts = example_line.split(' ')
parts[1] = parts[1].replace(':', '')
for i in are_numbers:
parts[i] = float(parts[i])
if parts[i].is_integer():
parts[i] = int(parts[i])
featues = [int(ft.split(':')[1]) for ft in parts[7:]]
return dict(zip(feature_names, parts[1:7] + featues))
这种方法有点笨拙,但可以完成工作:解析特征并尽可能转换为数字。输出看起来像:
{'ex_id': 20184824,
'hash': '57548fae76b0aa2f2e0d96c40ac6ae3057548faee00912d106fc65fc1fa92d68',
'clicked': 0,
'propensity': 1.416489e-07,
'slots': 6,
'candidates': 30,
'display_feature_1': 728,
'display_feature_2': 90,
'display_feature_3': 1,
'display_feature_4': 10,
'display_feature_5': 16,
'display_feature_6': 1,
'display_feature_7': 26,
'display_feature_8': 11,
'display_feature_9': 597,
'display_feature_10': 7}
接下来是产品示例。正如您提到的,问题是值的多次出现。我认为按频率聚合独特的 feature-value 对是明智的。信息不会丢失,但它可以帮助我们编码整洁的样本。那应该解决你的第二个问题。
import toolz # pip install toolz
def process_product(product_line):
# ${wasProduct1Clicked} exid:${exID} ${productFeat1_1}:${v1_1} ...
parts = product_line.split(' ')
meta = {'label': int(parts[0]),
'ex_id': int(parts[1].split(':')[1])}
# extract feautes that are ${productFeat1_1}:${v1_1} separated by ':' into a dictionary
features = [('product_feature_' + str(i), int(v))
for i, v in map(lambda x: x.split(':'), parts[2:])]
# count each unique value and transform them into
# feature_name X feature_value X feature_frequency
products = [dict(zip(['feature', 'value', 'frequency'], (*k, v)))
for k, v in toolz.countby(toolz.identity, features).items()]
# now merge the meta information into each product
return [dict(p, **meta) for p in products]
基本上提取每个示例的标签和特征(第 40 行的示例):
[{'feature': 'product_feature_11',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_12',
'value': 1,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_13',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_14',
'value': 2,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_15',
'value': 0,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_17',
'value': 2,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_21',
'value': 55,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_22',
'value': 14,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_22',
'value': 54,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_24',
'value': 3039,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_25',
'value': 721,
'frequency': 1,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_33',
'value': 386,
'frequency': 2,
'label': 0,
'ex_id': 19168103},
{'feature': 'product_feature_35',
'value': 963,
'frequency': 1,
'label': 0,
'ex_id': 19168103}]
因此,当您逐行处理流时,您可以决定是映射示例还是映射产品:
def process_stream(stream):
for content in stream:
if 'example' in content:
yield process_example(content)
else:
yield process_product(content)
我决定在这里做一个生成器,因为如果您决定不使用 pandas
,它将有利于以功能方式处理数据。否则列表压缩将
做你的炸鸡。
现在是有趣的部分:我们逐行阅读给定(示例)url 中的行,然后
将它们分配到相应的数据集(示例或产品)中。
我将在这里使用 reduce
,因为它很有趣 :-)。我不会详细说明 map/reduce
实际做了什么(这取决于您)。您始终可以改用简单的 for 循环。
import urllib.request
import toolz # pip install toolz
lines_stream = (line.decode("utf-8").strip()
for line in urllib.request.urlopen('http://www.cs.cornell.edu/~adith/Criteo/sample.txt'))
# if you care about concise but hacky approach you could do:
# blubb = list(toolz.partitionby(lambda x: 'hash' in x, process_file(lines_stream)))
# examples_only = blubb[slice(0, len(blubb), 2)]
# products_only = blubb[slice(1, len(blubb), 2)]
# but to introduce some functional approach lets implement a reducer
def dataset_reducer(datasets, content):
which_one = 0 if 'hash' in content else 1
datasets[which_one].append(content)
return datasets
# and process the stream using the reducer. Which results in two datasets:
examples_dataset, product_dataset = toolz.reduce(dataset_reducer, process_stream(lines), [[], []])
从这里您可以将您的数据集转换成一个整洁的数据框,您可以使用它来应用机器学习。当心 NaN
/缺失值、分布等。您可以将两个数据集与 merge
连接起来,以获得一大块 table 样本 X 特征。然后你将或多或少能够使用不同的方法,例如scikit-learn
.
import pandas
examples_dataset = pandas.DataFrame(examples_dataset)
product_dataset = pandas.concat(pandas.DataFrame(p) for p in product_dataset)
示例数据集
candidates clicked ... propensity slots
0 30 0 ... 1.416489e-07 6
1 23 0 ... 5.344958e-01 3
2 23 1 ... 1.774762e-04 3
3 28 0 ... 1.158855e-04 6
产品数据集(product_dataset.sample(10)
)
ex_id feature frequency label value
6 10244535 product_feature_21 1 0 10
9 37375474 product_feature_25 1 0 4
6 44432959 product_feature_25 1 0 263
15 62131356 product_feature_35 1 0 14
8 50383824 product_feature_24 1 0 228
8 63624159 product_feature_20 1 0 30
3 99375433 product_feature_14 1 0 0
9 3389658 product_feature_25 1 0 43
20 59461725 product_feature_31 8 0 4
11 17247719 product_feature_21 3 0 5
注意 product_dataset
。您可以 'pivot' 将行中的要素作为列(参见 reshaping docs)。
示例文件每个示例都有一些有趣的功能。在字典中展开,每个示例看起来像:
{'ex_id': int,
'hash': str,
'clicked': bool,
'propensity': float,
'slots': int,
'candidates': int,
'display_feature_1': [int],
'display_feature_2': [int],
'display_feature_3': [int],
'display_feature_4': [int],
'display_feature_5': [int],
'display_feature_6': [int],
'display_feature_7': [int],
'display_feature_8': [int],
'display_feature_9': [int],
'display_feature_10': [int],
'display_feature_11': [int],
'display_feature_12': [int],
'display_feature_13': [int],
'display_feature_14': [int],
'display_feature_15': [int],
'display_feature_16': [int],
'display_feature_17': [int],
'display_feature_18': [int],
'display_feature_19': [int],
'display_feature_20': [int],
'display_feature_21': [int],
'display_feature_22': [int],
'display_feature_23': [int],
'display_feature_24': [int],
'display_feature_25': [int],
'display_feature_26': [int],
'display_feature_27': [int],
'display_feature_28': [int],
'display_feature_29': [int],
'display_feature_30': [int],
'display_feature_31': [int],
'display_feature_32': [int],
'display_feature_33': [int],
'display_feature_34': [int],
'display_feature_35': [int]
}
其中功能 1-35 可能存在也可能不存在,并且可能重复也可能不重复。对于这种大小的数据集,合理的做法是将其存储为 tuple
的 list
,因此每个 tuple
对应一个示例 ID,如下所示:
(
int, # exid
str, # hash
bool, # clicked
float, # propensity
int, # slots
int, # candidates
dict # the display features
)
适合 35 个显示特征的 dict
结构是
{k+1 : [] for k in range(35)}
总的来说,这个示例数据结构可以概括为一个元组列表,每个元组中的最后一个元素是一个字典。
假设您在本地有 sample.txt
,您可以像这样填充此结构:
examples = []
with open('sample.txt', 'r') as fp:
for line in fp:
line = line.strip('\n')
if line[:7] == 'example':
items = line.split(' ')
items = [item.strip(':') for item in items]
examples.append((
int(items[1]), # exid
items[2], # hash
bool(items[3]), # clicked
float(items[4]), # propensity
int(items[5]), # slots
int(items[6]), # candidates
{k+1 : [] for k in range(35)} # the display features
))
for k in range(10):
examples[-1][6][k+1].append(int(items[k+7].split(':')[1]))
else:
items = line.split(' ')
while len(items) > 2:
keyval = items.pop()
key = int(keyval.split(':')[0])
val = int(keyval.split(':')[1])
examples[-1][6][key].append(val)
记录的这种数据结构可以转换为JSON,并读取到一个numpy数组。您可以轻松地根据每个元组中的任何元素对其进行排序,并快速迭代它。
处理多值记录项的方法是将它们存储在列表字典中。这使得收集他们的统计数据变得容易。