matplotlib 如何将分类散点标记移到xticks上方的左侧和右侧(每个类别有多个数据集)?

juzqafwq  于 2022-11-15  发布在  其他
关注(0)|答案(3)|浏览(96)

我有一个简单的Pandas Dataframe ,我想用matplotlib绘制它:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_excel('SAT_data.xlsx', index_col = 'State')

plt.figure()
plt.scatter(df['Year'], df['Reading'], c = 'blue', s = 25)
plt.scatter(df['Year'], df['Math'], c = 'orange', s = 25)
plt.scatter(df['Year'], df['Writing'], c = 'red', s = 25)

以下是我的出图:

我想将蓝色的数据点向左移动一点,红色的数据点向右移动一点,这样x轴上的每一年都有三个小列的散点数据,而不是三个数据集都重叠。我尝试过使用“verts”参数,但没有成功。有更好的方法吗?

35g0bw71

35g0bw711#

使用偏移变换将允许以点为单位而不是以数据为单位将散点移动一定量。优点是它们将始终彼此紧靠,与图形大小、缩放级别等无关。

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)
import matplotlib.transforms as transforms

year = np.random.choice(np.arange(2006,2017), size=(300) ) 
values = np.random.rand(300, 3)

plt.figure()

offset = lambda p: transforms.ScaledTranslation(p/72.,0, plt.gcf().dpi_scale_trans)
trans = plt.gca().transData

sc1 = plt.scatter(year, values[:,0], c = 'blue', s = 25, transform=trans+offset(-5))
plt.scatter(year, values[:,1], c = 'orange', s = 25)
plt.scatter(year, values[:,2], c = 'red', s = 25, transform=trans+offset(5))

plt.show()

广泛数字:

正常数字:

缩放

有人解释:
问题是我们想在数据坐标中的一些数据上添加一个点偏移量。虽然数据坐标会自动转换为显示坐标,使用transData(我们通常在表面上看不到),但添加一些偏移量需要我们改变转换。
我们通过增加一个偏移量来实现。虽然我们可以只增加一个像素的偏移量(显示坐标)、以点为单位添加偏移更方便,因此使用的单位与中给出的散点大小相同(它们的大小实际上是点的平方)。所以我们想知道有多少像素是p点?这是通过p除以ppi得出的(每英寸点数)以获得英寸,然后乘以dpi(每英寸点数)以获得显示像素。此计算在ScaledTranslation中完成。虽然每英寸点数原则上是可变的(并由dpi_scale_trans转换处理),但每英寸点数是固定的。Matplotlib使用72 ppi,这是一种typesetting standard

jtjikinw

jtjikinw2#

一个快速而肮脏的方法是创建一个小的偏移dx,并从蓝色点的x值中减去它,然后加上红色点的x值。

dx = 0.1
plt.scatter(df['Year'] - dx, df['Reading'], c = 'blue', s = 25) 
plt.scatter(df['Year'],      df['Math'], c = 'orange', s = 25) 
plt.scatter(df['Year'] + dx, df['Writing'], c = 'red', s = 25)

另一个选择是使用seaborn库中的stripplot函数。需要将原始 Dataframe 熔化为长格式,以便每行包含一个年份、一个测试和一个分数。然后生成stripplot,将年份指定为x。评分为y,测试为huesplit关键字参数控制将类别绘制为每个x的单独条带。这也是jitter参数,它将向x值添加一些噪声,以便它们占据一些小区域,而不是在一条垂直线上。

import pandas as pd
import seaborn as sns

# make up example data
np.random.seed(2017)
df = pd.DataFrame(columns = ['Reading','Math','Writing'], 
                  data = np.random.normal(540,30,size=(1000,3)))
df['Year'] = np.random.choice(np.arange(2006,2016),size=1000)

# melt the data into long form
df1 = pd.melt(df, var_name='Test', value_name='Score',id_vars=['Year'])

# make a stripplot
fig, ax = plt.subplots(figsize=(10,7))
sns.stripplot(data = df1, x='Year', y = 'Score', hue = 'Test', 
              jitter = True, split = True, alpha = 0.7, 
              palette = ['blue','orange','red'])

输出量:

bn31dyow

bn31dyow3#

这里是如何给出的代码可以适应工作与多个子情节,也是一个没有“中间列”的情况。
为了适应给定的代码,需要ax[n,p].transData而不是plt.gca().transDataplt.gca()指的是最后创建的子情节,而现在您需要对每个子情节进行转换。
另一个问题是,当只通过变换绘图时,matplotlib不会自动设置子图的下限和上限。在给定的例子中,在没有设置特定变换的情况下,会将点绘制在“中间”,并且绘图会围绕这些点“缩小”(在例子中为橙子)。
如果中心没有点,则需要用另一种方法设置限制。我想到的方法是在中心绘制一些虚拟点(设置缩放限制),然后再次删除这些虚拟点。
还要注意,散点的大小是以直径的平方(单位点)表示的。要得到接触点,你需要用平方根来表示它们的偏移量。

import matplotlib.pyplot as plt
from matplotlib import transforms
import numpy as np

# Set up data for reproducible example
year = np.random.choice(np.arange(2006, 2017), size=(100))
data = np.random.rand(4, 100, 3)
data2 = np.random.rand(4, 100, 3)

# Create plot and set up subplot ax loop
fig, axs = plt.subplots(2, 2, figsize=(18, 14))

# Set up offset with transform
offset = lambda p: transforms.ScaledTranslation(p / 72., 0, plt.gcf().dpi_scale_trans)

# Plot data in a loop
for ax, q, r in zip(axs.flat, data, data2):
    temp_points = ax.plot(year, q, ls=' ')
    for pnt in temp_points:
        pnt.remove()
    ax.plot(year, q, marker='.', ls=' ', ms=10, c='b', transform=ax.transData + offset(-np.sqrt(10)))
    ax.plot(year, r, marker='.', ls=' ', ms=10, c='g', transform=ax.transData + offset(+np.sqrt(10)))

plt.show()

相关问题