• nlp入门(二) :商品信息可视化与文本分析实战


    源码请到:自然语言处理练习: 学习自然语言处理时候写的一些代码 (gitee.com)

    数据来源:麦卡里价格建议挑战Mercari Price Suggestion Challenge | Kaggle

    如果不会使用魔法可以使用百度云

    链接:https://pan.baidu.com/s/1EM2MwjX4bLlypLSIJYZqeg?pwd=xqs0
    提取码:xqs0

    一、加载数据集

    拿到数据集后首先对数据集的数据进行一些显示展示,来了解数据集

    复制代码
    # 设置pandas显示配置
    pd.set_option('display.max_columns', 1000)
    pd.set_option('display.width', 1000)
    pd.set_option('display.max_colwidth', 1000)
    
    train = pd.read_csv('data/train.tsv', sep='\t')
    test = pd.read_csv('data/test.tsv', sep='\t')
    # 查看数据集大小
    print(train.shape)
    print(test.shape)
    # 查看数据集列名
    print(train.dtypes)
    # 查看数据集前几行
    print(train.head())
    # 查看商品价格信息
    print(train.price.describe())
    复制代码

    二、分析影响价格的因素

    经过第一步已经大致了解了数据集的内容以及价格的范围大小及均值,下面我们进一步对影响价格的因素进行分析

    2.1 对价格进行对数变换,比较变换后价格的分布情况

    由于价格的分布十分的散乱,所以对价格进行对数变换处理

    复制代码
    # 对价格进行对数变换,比较转换前和转换后的分布情况
    plt.subplot(1, 2, 1)
    (train['price']).plot.hist(bins=50, figsize=(20, 10), edgecolor='white', range=[0, 250])
    plt.xlabel('price+', fontsize=17)
    plt.ylabel('frequency', fontsize=17)
    plt.tick_params(labelsize=15)
    plt.title('Price Distribution - Training Set', fontsize=17)
    
    plt.subplot(1, 2, 2)
    np.log(train['price'] + 1).plot.hist(bins=50, figsize=(20, 10), edgecolor='white')
    plt.xlabel('log(price+1)', fontsize=17)
    plt.ylabel('frequency', fontsize=17)
    plt.tick_params(labelsize=15)
    plt.title('Log(Price) Distribution - Training Set', fontsize=17)
    plt.show()
    复制代码

    可以看出,原数据十分不均衡,高价区区间大数量少,经过对数处理的数据更加符合正态分布

    2.2 包邮对价格的影响

    接下来分析商户包邮是否对价格产生影响

    # 运费承担:大概有55%卖家承担运费
    print(train.shipping.value_counts() / len(train))

     可以看出,大概有百分之五十五的商户包邮

    复制代码
    # 看下运费不同情况下的价格变化(包邮的价格贵一些)
    prc_shipBySeller = train.loc[train.shipping == 0, 'price']
    prc_shipByBuyer = train.loc[train.shipping == 1, 'price']
    fig, ax = plt.subplots(figsize=(20, 10))
    ax.hist(np.log(prc_shipBySeller + 1), color='#8CB4E1', alpha=1.0, bins=50, label='Price when Seller pays Shipping')
    ax.hist(np.log(prc_shipByBuyer + 1), color='#007D00', alpha=0.7, bins=50, label='Price when Buyer pays Shipping')
    ax.set(title='Histogram Comparison', ylabel='% of Dataset in Bin')
    plt.legend()
    plt.xlabel('log(price+1)', fontsize=17)
    plt.ylabel('frequency', fontsize=17)
    plt.title('Price Distribution by Shipping Type', fontsize=17)
    plt.tick_params(labelsize=15)
    plt.show()
    复制代码

     可以看出,包邮的整体价格比不包邮的价格贵一些

    2.3 商品类别对价格的影响

    首先我们统计下商品的类别

    # 商品类别划分
    print('There are %d unique values in the category column' % train['category_name'].nunique())
    print(train['category_name'].value_counts()[:5])
    print('There are %d items that do not have a label' % train['category_name'].isnull().sum())

    可以看出总共有1287种商品,并且展示了数量前五的商品,还有6327件商品没有类别标签,我们处理的时候就忽略这些商品

    商品类别太多了,可以看到商品类别结构为主类/子类1/子类2的格式,我们进行拆分,将类别合并一些

    复制代码
    # 商品类别太多了,合并一下
    def split_cat(text):
        try:
            return text.split("/")
        except:
            return "No Label", "No Label", "No Label"
    
    
    train['general_cat'], train['subcat_1'], train['subcat_2'] = zip(*train['category_name'].apply(lambda x: split_cat(x)))
    print(train.head())
    test['general_cat'], test['subcat_1'], test['subcat_2'] = zip(*test['category_name'].apply(lambda x: split_cat(x)))
    print('There are %d unique general_cat' % train['general_cat'].nunique())
    print('There are %d unique first sub-categories' % train['subcat_1'].nunique())
    print('There are %d unique second sub-categories' % train['subcat_2'].nunique())
    复制代码

    可以看出总共有11个主类,114个子类1,871个子类2

    接下来分析下主类的分布情况

    复制代码
    # 主类别分布情况
    x = train['general_cat'].value_counts().index.values.astype('str')
    y = train['general_cat'].value_counts().values
    pct = [('%.2f' % (v * 100)) + '%' for v in (y / len(train))]
    tracel = go.Bar(x=x, y=y, text=pct)
    layout = dict(title="Number of Items by Main Category", yaxis=dict(title='Count'), xaxis=dict(title='Category'))
    fig = dict(data=[tracel], layout=layout)
    py.iplot(fig)
    复制代码

    可以看出,大量商品是关于女这种类别的,占据了百分之四十五,第二多的是化妆品,第三多的是孩子。

    子类的数量很多,我们展示前15个子类的分布

    复制代码
    # 前15个子类别分布情况
    x = train['subcat_1'].value_counts().index.values.astype('str')[:15]
    y = train['subcat_1'].value_counts().values[:15]
    pct = [('%.2f' % (v * 100)) + '%' for v in (y / len(train))][:15]
    tracel = go.Bar(x=x, y=y, text=pct, marker=dict(color=y, colorscale='Portland', showscale=True, reversescale=False))
    layout = dict(title="Number of Items by Sub Category(Top 15)", yaxis=dict(title='Count'),
                  xaxis=dict(title='SubCategory'))
    fig = dict(data=[tracel], layout=layout)
    py.iplot(fig)
    复制代码

     接下来看看不同主类商品的价格区间

    复制代码
    # 不同类型商品价格浮动区间
    general_cats = train['general_cat'].unique()
    x = [train.loc[train['general_cat'] == cat, 'price'] for cat in general_cats]
    data = [go.Box(x=np.log(x[i] + 1), name=general_cats[i]) for i in range(len(general_cats))]
    layout = dict(title='Price Distribution by General Category', yaxis=dict(title='Frequency'),
                  xaxis=dict(title='Category'))
    fig = dict(data=data, layout=layout)
    py.iplot(fig)
    复制代码

     2.4 品牌分布情况

    分析一下品牌的分布情况

    复制代码
    # 前10品牌名称的数据分布
    x = train['brand_name'].value_counts().index.values.astype('str')[:10]
    y = train['brand_name'].value_counts().values[:10]
    tracel = go.Bar(x=x, y=y, marker=dict(color=y, colorscale='Portland', showscale=True, reversescale=False))
    layout = dict(title="Top 10 Brand by Number of Items", yaxis=dict(title='Count'), xaxis=dict(title='Brand Name'))
    fig = dict(data=[tracel], layout=layout)
    py.iplot(fig)
    复制代码

    2.5 商品描述长度对商品的影响

    统计商品描述的长度,然后研究其对商品的影响

    复制代码
    # 商品描述对价格的影响
    def wordCount(text):
        try:
            text = text.lower()
            regex = re.compile('[' + re.escape(string.punctuation) + '0-9\\r\\t\\n]')
            txt = regex.sub(' ', text)
            words = [w for w in txt.split(" ") if w not in stop_words.STOP_WORDS and len(w) > 3]
            return len(words)
        except:
            return 0
    
    
    train['desc_len'] = train['item_description'].apply(lambda x: wordCount(x))
    test['desc_len'] = test['item_description'].apply(lambda x: wordCount(x))
    print(train.head())
    
    df = train.groupby('desc_len')['price'].mean().reset_index()
    tracel = go.Scatter(x=df['desc_len'], y=np.log(df['price'] + 1), mode='lines+markers', name='lines+markers')
    layout = dict(title='Average Log(Price) by Description Length', yaxis=dict(title='Average Log(Price)'),
                  xaxis=dict(title='Description Length'))
    fig = dict(data=[tracel], layout=layout)
    py.iplot(fig)
    复制代码

     

    可以看到商品描述适中价格越高,描述短的可能因为功能简单所以价格低,描述长的可能因为小众所以价格低

    三、商品描述关键字

    3.1 统计常用关键字

    统计一下商品描述中常用的关键字,注意,有的商品没有商品描述,需要去掉

    复制代码
    print(train.item_description.isnull().sum())
    # 去掉缺失值
    train = train[pd.notnull(train['item_description'])]
    # 提取每种品牌的描述关键词
    tokenize = nltk.data.load('tokenizers/punkt/english.pickle')
    cat_desc = dict()
    for cat in general_cats:
        text = ' '.join(train.loc[train['general_cat'] == cat, 'item_description'].values)
        cat_desc[cat] = tokenize.tokenize(text)
    
    # 统计常用关键词
    flat_lst = [item for sublist in list(cat_desc.values()) for item in sublist]
    allWordsCount = Counter(flat_lst)
    all_top10 = allWordsCount.most_common(20)
    x = [w[0] for w in all_top10]
    y = [w[1] for w in all_top10]
    tracel = go.Bar(x=x, y=y)
    layout = dict(title='Word Frequency', yaxis=dict(title='Count'), xaxis=dict(title='Word'))
    fig = dict(data=[tracel], layout=layout)
    py.iplot(fig)
    复制代码

     

     

     

    3.2 分别展示不同商品的关键字

     首先将商品描述进行分词,去掉停用词

    复制代码
    # 展示不同商品的关键词
    stop = set(stopwords.words('english'))
    
    
    def tokenize(text):
        try:
            regex = re.compile('[' + re.escape(string.punctuation) + '0-9\\r\\t\\n]')
            text = regex.sub(' ', text)
    
            tokens_ = [word_tokenize(s) for s in sent_tokenize(text)]
            tokens = []
            for token_by_sent in tokens_:
                tokens += token_by_sent
            tokens = list(filter(lambda t: t.lower() not in stop, tokens))
            filtered_tokens = [w for w in tokens if re.search('[a-zA-Z]', w)]
            filtered_tokens = [w.lower() for w in filtered_tokens if len(w) >= 3]
            return filtered_tokens
        except TypeError as e:
            print(text, e)
    
    
    train['tokens'] = train['item_description'].map(tokenize)
    test['tokens'] = test['item_description'].map(tokenize)
    
    train.reset_index(drop=True, inplace=True)
    test.reset_index(drop=True, inplace=True)
    for description, tokens in zip(train['item_description'].head(), train['tokens'].head()):
        print('description:', description)
        print('tokens:', tokens)
        print()
    复制代码

    复制代码
    cat_desc = dict()
    for cat in general_cats:
        text = ' '.join(train.loc[train['general_cat'] == cat, 'item_description'].values)
        cat_desc[cat] = tokenize(text)
    
    cat_desc100 = dict()
    for key, value in cat_desc.items():
        cat_desc100[key] = Counter(value).most_common()
    
    
    def generate_wordcloud(tup):
        wordcloud = WordCloud(background_color='white', max_words=50, max_font_size=40, random_state=42).generate(str(tup))
        return wordcloud
    
    
    fig, axes = plt.subplots(len(cat_desc100) // 2 + 1, 2, figsize=(30, 15))
    
    for i, (key, cat) in enumerate(cat_desc100.items()):
        ax = axes[i // 2, i % 2]
        ax.imshow(generate_wordcloud(cat), interpolation='bilinear')
        ax.axis('off')
        ax.set_title("%s Top 100" % key, fontsize=12)
    
    plt.show()
    复制代码

    对每个类别提取数量最多的前100个关键字统计词频生成词云

    四、tfidf算法

    可以看出不同类别出现的关键字有很多是相似的,不能代表这种类别的商品,所以我们使用tf-idf算法进行关键字的挖掘,tf-idf基本思想是词在本文章中出现的次数越多在其他文章中出现的次数越少越可能是关键字。首先将描述数据扩展到180000维度,在进行tf-idf打分

    复制代码
    # tf-idf
    vectorizer = TfidfVectorizer(min_df=10, max_features=180000, tokenizer=tokenize, ngram_range=(1, 2))
    all_desc = np.append(train['item_description'].values, test['item_description'].values)
    vz = vectorizer.fit_transform(list(all_desc))
    print(vz.shape)
    
    tfidf = dict(zip(vectorizer.get_feature_names_out(), vectorizer.idf_))
    tfidf = pd.DataFrame(columns=['tfidf']).from_dict(dict(tfidf), orient='index')
    tfidf.columns = ['tfidf']
    print(tfidf.sort_values(by=['tfidf'], ascending=True).head(10))
    print(tfidf.sort_values(by=['tfidf'], ascending=False).head(10))
    复制代码

     

     

    可以看出停用词的得分值基本上都比较低,因为他们虽然频率高但是不具备什么代表性的价值,而另一批的词得分就很高,可以作为关键词来分析

    接下来使用SVD降维将特征的维度降到50,然后使用t-SNE将维度降维到2进行展示

    复制代码
    trn = train.copy()
    tst = test.copy()
    trn['is_train'] = 1
    tst['is_train'] = 0
    sample_sz = 15000
    # 采样
    combined_df = pd.concat([trn, tst])
    combined_sample = combined_df.sample(n=sample_sz)
    vz_sample = vectorizer.fit_transform(list(combined_sample['item_description']))
    # SVD 降维
    n_comp = 30
    svd = TruncatedSVD(n_components=n_comp, random_state=42)
    svd_tfidf = svd.fit_transform(vz_sample)
    # t-SNE降维
    tsne_model = TSNE(n_components=2, verbose=1, random_state=42, n_iter=500)
    tsne_tfidf = tsne_model.fit_transform(svd_tfidf)
    
    plot_tfidf = bp.figure(width=700, height=600, title='tf-idf clustring of the item description',
                           tools='pan, wheel_zoom, box_zoom, reset, hover', x_axis_type=None, y_axis_type=None,
                           min_border=1)
    combined_sample.reset_index(inplace=True, drop=True)
    tfidf_df = pd.DataFrame(tsne_tfidf, columns=['x', 'y'])
    tfidf_df['description'] = combined_sample['item_description']
    tfidf_df['tokens'] = combined_sample['tokens']
    tfidf_df['category'] = combined_sample['general_cat']
    plot_tfidf.scatter(x='x', y='y', source=tfidf_df, alpha=0.7)
    hover = plot_tfidf.select(dict(type=HoverTool))
    hover.tooltips = {'description': '@description', 'tokens': '@tokens', 'category': '@category'}
    show(plot_tfidf)
    复制代码

     关键词比较接近的就会被绘制在一个点位置

    五、分类

    5.1 使用聚类算法对上面数据的点可以进行分类

    复制代码
    # 聚类分堆
    num_clusters = 10
    kmeans_model = MiniBatchKMeans(n_clusters=num_clusters, init='k-means++', n_init=1, init_size=10000, batch_size=1000,
                                   verbose=0, max_iter=1000)
    kmeans_model.fit(vz_sample)
    kmeans_clusters = kmeans_model.predict(vz_sample)
    kmeans_distances = kmeans_model.transform(vz_sample)
    tsne_kmeans = tsne_model.fit_transform(kmeans_distances)
    
    kmeans_df = pd.DataFrame(tsne_kmeans, columns=['x', 'y'])
    kmeans_df['cluster'] = kmeans_clusters
    kmeans_df['description'] = combined_sample['item_description']
    kmeans_df['category'] = combined_sample['general_cat']
    plot_kmeans = bp.figure(width=700, height=600, title='KMeans clustering of the description',
                            tools='pan, wheel_zoom, box_zoom, reset, hover', x_axis_type=None, y_axis_type=None,
                            min_border=1)
    print(kmeans_clusters)
    colormap = {'0': 'red', '1': 'green', '2': 'blue', '3': 'black', '4': 'yellow', '5': 'pink', '6': 'purple', '7': 'grey',
                '8': 'brown', '9': 'orange'}
    
    
    def get_color(num):
        if num == 0:
            return 'red'
        elif num == 1:
            return 'green'
        elif num == 2:
            return 'blue'
        elif num == 3:
            return 'black'
        elif num == 4:
            return 'yellow'
        elif num == 5:
            return 'pink'
        elif num == 6:
            return 'purple'
        elif num == 7:
            return 'grey'
        elif num == 8:
            return 'brown'
        elif num == 9:
            return 'orange'
    
    
    color = pd.Series(kmeans_clusters).apply(get_color)
    source = ColumnDataSource(
        data=dict(x=kmeans_df['x'], y=kmeans_df['y'], color=color, description=kmeans_df['description'],
                  category=kmeans_df['category'], cluster=kmeans_df['cluster']))
    plot_kmeans.scatter(x='x', y='y', color='color', source=source)
    hover = plot_kmeans.select(dict(type=HoverTool))
    hover.tooltips = {'description': '@description', 'category': '@category', 'cluster': '@cluster'}
    show(plot_kmeans)
    复制代码

     5.2 LDA主题模型分类

    除了聚类算法外,也可以使用LDA主题模型进行分类

    复制代码
    # LDA分堆
    cvectorizer = CountVectorizer(min_df=4, max_features=180000, tokenizer=tokenize, ngram_range=(1, 2))
    cvz = cvectorizer.fit_transform(combined_sample['item_description'])
    lda_model = LatentDirichletAllocation(n_components=10, learning_method='online', max_iter=20, random_state=42)
    X_topics = lda_model.fit_transform(cvz)
    
    # 获取主题
    n_top_words = 10
    topic_summaries = []
    topic_word = lda_model.components_
    vocab = cvectorizer.get_feature_names_out()
    for i, topic_dist in enumerate(topic_word):
        topic_words = np.array(vocab)[np.argsort(topic_dist)][:-(n_top_words + 1):-1]
        topic_summaries.append(' '.join(topic_words))
        print('Topic {}:{}'.format(i, '|'.join(topic_words)))
    
    tsne_lda = tsne_model.fit_transform(X_topics)
    unnormalized = np.matrix(X_topics)
    doc_topic = unnormalized / unnormalized.sum(axis=1)
    lda_keys = []
    for i, tweet in enumerate(combined_sample['item_description']):
        lda_keys += [doc_topic[i].argmax()]
    
    lda_df = pd.DataFrame(tsne_lda, columns=['x', 'y'])
    lda_df['description'] = combined_sample['item_description']
    lda_df['category'] = combined_sample['general_cat']
    lda_df['topic'] = lda_keys
    lda_df['topic'] = lda_df['topic'].map(int)
    plot_lda = bp.figure(width=700, height=600, title='LDA topic visualization',
                         tools='pan, wheel_zoom, box_zoom, reset, hover', x_axis_type=None, y_axis_type=None, min_border=1)
    source = ColumnDataSource(
        data=dict(x=lda_df['x'], y=lda_df['y'], color=color, description=lda_df['description'],
                  topic=lda_df['topic'], category=lda_df['category']))
    plot_lda.scatter(x='x', y='y', color='color', source=source)
    hover = plot_lda.select(dict(type=HoverTool))
    hover.tooltips = {'description': '@description', 'topic': '@topic', 'category': '@category'}
    show(plot_lda)
    复制代码

     把不同关键字分为了十个主题

     

  • 相关阅读:
    Kafka 万亿级消息实践之资源组流量掉零故障排查分析
    gabse 8a常见问题
    【译】ConfigureAwait FAQ
    学习——数据结构
    Linux入门之管理 user 和 group
    Web端与App端自动化测试框架
    谁用谁夸,超好用的电子期刊制作网站
    生还是不生? SpringBoot3 版本有起飞前兆,最小依赖Java17!
    逆向-beginners之float联合体
    Audio Hijack for Mac 激活版:音频录制与处理软件
  • 原文地址:https://www.cnblogs.com/zhangshihang/p/17613582.html