【问题标题】:Unable to plot histogram with time on x-axis using Matplotlib and Python无法使用 Matplotlib 和 Python 在 x 轴上随时间绘制直方图
【发布时间】:2023-03-31 09:35:01
【问题描述】:

我正在尝试绘制用户在一天中的特定时间发推文的次数。我计划将这些绘制在一个直方图/条形图上,其中包含 24 个“箱”——每小时一个。

我将 Pandas 数据框中的数据分为两列 - 推文和推文时间(作为日期时间对象)。

我已将时间列转换为 Pandas 时间,但是我很难正确绘制。如果我将 bin 的值设置为 24,那么我会得到下面的图表 (here),它看起来不正确。首先图表看起来不对,其次 x 轴的格式很糟糕。

我想尝试解决这两个问题。首先数据没有正确绘制,其次水平轴格式不正确。

我使用 Google 表格绘制了数据,正确的图表应该类似于 this。我不介意这些值是总体积的百分比还是绝对体积。

可以在此处找到生成图的代码。 generate_data.pyplot_data.py

非常感谢任何帮助。

plot_data.py

import datetime
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import random

import generate_data


screen_name = "@joebiden"
data = generate_data.get_data(screen_name, save_output=True)

df = pd.DataFrame(data)
df["Time"]= pd.to_datetime(data["Time"], format="%H:%M") 

fig, ax = plt.subplots(1,1)
bin_list = [datetime.time(x) for x in range(24)]

ax.hist(df["Time"], bins=24, color='lightblue')
plt.show()

generate_data.py

import json
import re
from datetime import datetime

import tweepy

import common_words
import twitter_auth 



def create_connection():
    auth = tweepy.OAuthHandler(twitter_auth.CONSUMER_KEY, twitter_auth.CONSUMER_SECRET)
    auth.set_access_token(twitter_auth.ACCESS_KEY, twitter_auth.ACCESS_SECRET)
    return tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True)


def retrieve_next_set_of_tweets(screen_name, api, count,max_id):
    '''Return next 200 user tweets'''
    return api.user_timeline(screen_name=screen_name,count=count, tweet_mode='extended', max_id=max_id)


def get_tweet_times(screen_name, api):
    user_tweet_count = api.get_user(screen_name).statuses_count

    all_tweets = {'Tweet':[], 'Time':[]}
    block_of_tweets = api.user_timeline(screen_name=screen_name,count=200, tweet_mode='extended')
    all_tweets["Tweet"].extend([tweet.full_text for tweet in block_of_tweets])
    all_tweets["Time"].extend([tweet.created_at for tweet in block_of_tweets])
    oldest = block_of_tweets[-1].id - 1

    
    while block_of_tweets:    
        try:
            block_of_tweets = retrieve_next_set_of_tweets(screen_name, api, 200, oldest)
            oldest = block_of_tweets[-1].id - 1
        except IndexError: #Reached limit of 3245
            pass
        # all_tweets.update({tweet.full_text: tweet.created_at.time() for tweet in block_of_tweets}) 
        all_tweets["Tweet"].extend([tweet.full_text for tweet in block_of_tweets])
        all_tweets["Time"].extend([tweet.created_at for tweet in block_of_tweets])
 
    return all_tweets


def get_all_tweets(screen_name, api):
    user_tweet_count = api.get_user(screen_name).statuses_count

    all_tweets = []
    block_of_tweets = api.user_timeline(screen_name=screen_name,count=200, tweet_mode='extended')
    all_tweets.extend([tweet.full_text for tweet in block_of_tweets])
    oldest = block_of_tweets[-1].id - 1
    

    while block_of_tweets:    
        try:
            block_of_tweets = retrieve_next_set_of_tweets(screen_name, api, 200, oldest)
            oldest = block_of_tweets[-1].id - 1
        except IndexError: #Reached limit of 3245
            pass

        all_tweets.extend([tweet.full_text for tweet in block_of_tweets]) 
    return all_tweets


def parse_all_tweets(tweet_list, max_words_to_show=50):
    tweet_dict = {}
    regex = re.compile('[^a-zA-Z ]')
    for tweet in tweet_list: 
        text = regex.sub("", tweet).lower().strip().split()
        
        for word in text:
            if word in common_words.MOST_COMMON_ENGLISH_WORDS: continue
            if word in tweet_dict.keys():
                tweet_dict[word] += 1
            else:
                if len(tweet_dict.items()) == max_words_to_show:
                    return tweet_dict
                tweet_dict[word] = 1
    return tweet_dict
    
    
def get_data(screen_name, words_or_times="t", save_output=False):
    api = create_connection()
    print(f"...Getting max of 3245 tweets for {screen_name}...")

    if words_or_times == "t":
        all_tweets = get_tweet_times(screen_name, api)
        suffix = "tweet_times"
        
    elif words_or_times == "w":
        suffix = "ranked_words"
        parsed_tweets = parse_all_tweets(get_all_tweets(screen_name, api))
        parsed_tweets = {k:v for k,v in sorted(parsed_tweets.items(), key=lambda item: item[1], reverse=True)}

    else:
        return "...Error. Please enter 't' or 'w' to signify 'times' or 'words'."

    if save_output:
        f_name = f"{screen_name}_{suffix}.json"
        with open(f_name, "w") as f:
            json.dump(all_tweets if words_or_times == "t" else parsed_tweets, f, indent=4, default=str)
        
        print(f"...Complete! File saved as '{f_name}'")

    return all_tweets if words_or_times == "t" else parsed_tweets


if __name__ == "__main__":
    get_data(screen_name="@joebiden", save_output=True) 

【问题讨论】:

  • 能给几行df = pd.DataFrame(data)吗?另外,请检查您确实有日期。
  • 这里是 df 的前 5 行。 0 RT @Transition46:我们团结一致... 2020-11-08 14:28:23 1 发自内心:谢谢。 https:... 2020-11-08 02:20:00 2 一个国家团结起来。\n\n一个国家加强了。\n\n... 2020-11-08 02:10:00 3 全心全意稳定双手,带着信念... 2020-11-08 02:08:00 4 今晚,全世界都在注视着美国。 ... 2020-11-08 02:05:00```
  • 当我输出 df["Time"] 的前 5 条记录时,我得到以下信息。它们是 但仍显示日期和时间,即使我尝试仅在 plot_data.py 的第 14 行将它们格式化为时间。 0 2020-11-08 14:28:23 1 2020-11-08 02:20:00 2 2020-11-08 02:10:00 3 2020-11-08 02:08:00 4 2020-11-08 02:05:00

标签: python pandas matplotlib plot


【解决方案1】:

我已经尝试绘制data,您在评论中分享了 Serge de Gosson de Varennes 的答案。我唯一需要在plot_data.py 脚本中更改的是日期format,我在其中添加了秒数。其余的按预期工作,x 轴的时间处理正确。

为了方便起见,这里是使用pd.Series.hist 创建直方图的示例。包含weights 参数以生成带有百分比的图表。大部分代码用于格式化:

import numpy as np                 # v 1.19.2
import pandas as pd                # v 1.1.3
import matplotlib.pyplot as plt    # v 3.3.2
import matplotlib.dates as mdates

# Import data from html table into pandas dataframe
url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTc97VEzlfDP_jEkjC7dTbJzcLBLDQeFwPMg6E36BaiH5qkhnedSz8wsVGUMyW6kt85rD20BcTMbvqp/pubhtml'
table, = pd.read_html(url, header=[1], index_col=1)
df = table.iloc[:, 1:]

# Save time variable as a pandas series of datetime dtype
time_var = pd.to_datetime(df['Time'], format='%H:%M:%S')

# Plot variable with pandas histogram function
ax = time_var.hist(bins=24, figsize=(10,5), grid=False, edgecolor='white', zorder=2,
                   weights=np.ones(time_var.size)/time_var.size)

# Format x and y-axes tick labels
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
ax.yaxis.set_major_formatter('{x:.1%}')

# Additional formatting
alpha = 0.3
ax.grid(axis='y', zorder=1, color='black', alpha=alpha)
for spine in ['top', 'right', 'left']:
    ax.spines[spine].set_visible(False)
ax.spines['bottom'].set_alpha(alpha)
ax.tick_params(axis='x', which='major', color=[0, 0, 0, alpha])
ax.tick_params(axis='y', which='major', length=0)
ax.set_title('Tweets sent per hour of day in UTC', fontsize=14, pad=20)
ax.set_ylabel('Relative frequency (% of total)', size=12, labelpad=10)

plt.show()

由于此直方图中的计数分布在 24 小时内,您可能会注意到条形的高度与您作为参考共享的图像中的直方图中的高度略有不同,其中计数似乎被分组为 23 bins 而不是 24 个。



参考:this answer by ImportanceOfBeingErnest

【讨论】:

    【解决方案2】:

    好的。所以你只想从你的约会时间开始。换个试试

    df["Time"]= pd.to_datetime(data["Time"], format="%H:%M")
    

    df['Time'] = pd.to_datetime(df['Time'],format= '%H:%M' ).dt.time
    

    【讨论】:

    • 感谢您的帮助。这已正确格式化时间,例如14:28:23, 02:20:00,02:10:00 等但是现在我在尝试绘制这些时遇到错误。当脚本尝试运行ax.hist(df["Time"], bins=23, color='lightblue')时,我得到的错误是TypeError: '<' not supported between instances of 'datetime.time' and 'float'
    • 您的数据集中是否存在缺失值?检查那个。如果是这样,请在做任何其他事情之前对待他们。
    • 看起来没有数据丢失。我已将 CSV 转储到 Google 表格中并将其发布为网页 here,并且似乎没有任何行的任何一列都有缺失值。
    • 我暂时删除了除前 10 条记录之外的所有记录,并且在尝试绘制 TypeError: '<' not supported between instances of 'datetime.time' and 'float' 时遇到了同样的错误,即使绝对没有数据丢失 - imgur.com/a/ztABgZf
    猜你喜欢
    • 1970-01-01
    • 2019-04-17
    • 2015-01-07
    • 2022-08-08
    • 2020-09-18
    • 2021-10-07
    • 2015-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多