hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

CPU使用率、メモリ使用量、ディスクアクセスの高いプロセスの調べ方

1 はじめに

システムを運用している中で、コマンドの応答が遅くなるなどの問題が発生する場合があります。このような状況では、CPUやメモリなどのコンピュータリソースを多く使用しているプロセスを特定し、問題の原因を特定する必要があります。リソースを多く消費しているプロセスを見つける方法はいくつかありますが、ここではpsコマンドを使用してCPU使用率、メモリ使用率、およびディスクアクセスが高いプロセスを調べる方法について説明します。なお、psコマンドの詳細な使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

3 CPU使用率の高いプロセスを調べる方法(--sort=-%cpu)

3.1 事前準備

私の環境は、CPU使用率の高いプロセスが少なく実行結果を確認しずらいので、意図的にCPU使用率の高いプロセスを起動しました。stress-ngコマンドを実行して、CPU使用率が10%,20%のプロセスをそれぞれ1つ起動します。なお、stress-ngコマンドのインストール方法、使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# stress-ng -k -c 1 -l 10 -q &
[1] 1515
[root@server ~]# stress-ng -k -c 1 -l 20 -q &
[2] 1517

3.2 動作確認

プロセスのCPU使用率を降順に表示するには、psコマンドのオプションに--sort=-%cpuと指定します。以下の実行例を確認すると、プロセスのCPU使用率が降順に表示されていることがわかります。CPU使用率を昇順に表示するには、--sortオプションに+%cpuを指定します。

[root@server ~]# ps ax --sort=-%cpu -o command,pid,%cpu|head -n 5
COMMAND                       PID %CPU
stress-ng -k -c 1 -l 20 -q   1518 21.2
stress-ng -k -c 1 -l 10 -q   1516 10.5
/usr/lib/systemd/systemd --     1  1.5
/usr/bin/python2 -Es /usr/s   616  1.3

pkillコマンドを実行して、stressプロセスを終了します。

[root@server ~]# pkill stress-ng

4 メモリ使用量の高いプロセスを調べる方法(--sort=-rss)

4.1 事前準備

stressコマンドを実行して、512M,256Mのメモリを獲得するプロセスをそれぞれ1つ起動します。なお、stressコマンドの詳細な使い方は、stressコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# stress -m 1 --vm-bytes 256M --vm-hang 0 -q &
[1] 1543
[root@server ~]# stress -m 1 --vm-bytes 512M --vm-hang 0 -q &
[2] 1545

4.2 動作確認

プロセスが使用しているメモリ使用量(RSS)を降順に表示します。以下の実行例を確認すると、プロセスのメモリ使用量が降順に表示されていることがわかります。

[root@server ~]# ps ax --sort=-rss -o command,pid,ppid,vsz,rss|head -n 5
COMMAND                       PID  PPID    VSZ   RSS
stress -m 1 --vm-bytes 512M  1546  1545 531600 524456
stress -m 1 --vm-bytes 256M  1544  1543 269456 262288
/usr/bin/python2 -Es /usr/s   616     1 359088 29808
/usr/bin/python2 -Es /usr/s   937     1 574280 19484

pkillコマンドを実行して、stressプロセスを終了します。

[root@server ~]# pkill stress
[1]-  Terminated              stress -m 1 --vm-bytes 256M --vm-hang 0 -q
[2]+  Terminated              stress -m 1 --vm-bytes 512M --vm-hang 0 -q

5 ディスクアクセスの割合が高いプロセスを調べる方法

iotopコマンドを使って、ディスクアクセスが多いプロセスを調べてみます。なお、iotopコマンドの詳細な使い方は、iotopコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# iotop

stressコマンドを実行します。

[root@server ~]# stress -d 1 -q &
[1] 4682

以下の実行例を確認すると、stressコマンドで起動したプロセスがiotopコマンド実行結果の最上位に表示されていることがわかります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

stress-ngコマンドの使い方(UDP編)

1 はじめに

stress-ngコマンドは、CPU、メモリ、ディスク、ネットワークなど、さまざまなリソースに対して負荷テストを行うためのツールです。stressコマンドと比較して、より多くの種類のリソースに対して負荷をかけることができます。本記事ではUDPに関する使い方を説明します。なお、stressコマンド、およびstress-ngコマンドの基本的な使い方は、以下の記事を参照してください。

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

3 パッケージのインストール方法

stress-ngパッケージをインストールします。なお、dnfコマンドの使い方は、dnfコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# dnf -y install stress-ng

stress-ngコマンドの版数を確認します。

[root@server ~]# stress-ng -V
stress-ng, version 0.15.00 (gcc 11.3, x86_64 Linux 5.14.0-284.11.1.el9_2.x86_64) ?諮

なお、AlmaLinux 9.1以前は、stress-ngパッケージはepelリポジトリにあるので、まずepel-releaseパッケージをインストールします。そのあと、stress-ngパッケージをインストールします。

[root@server ~]# dnf -y install epel-release
[root@server ~]# dnf -y install stress-ng

4 オプション一覧

stress-ngコマンドのUDPオプションには以下のものがあります。

[root@server ~]# man stress-ng
-snip-
       --udp N
              start  N workers that transmit data using UDP. This involves a pair of client/server processes perform‐
              ing rapid connect, send and receives and disconnects on the local host.

       --udp-domain D
              specify the domain to use, the default is ipv4. Currently ipv4, ipv6 and unix are supported.

       --udp-if NAME
              use network interface NAME. If the interface NAME does not exist, is not up or does not support the do‐
              main then the loopback (lo) interface is used as the default.

       --udp-gro
              enable UDP-GRO (Generic Receive Offload) if supported.

       --udp-lite
              use the UDP-Lite (RFC 3828) protocol (only for ipv4 and ipv6 domains).

       --udp-ops N
              stop udp stress workers after N bogo operations.

       --udp-port P
              start  at port P. For N udp worker processes, ports P to P - 1 are used. By default, ports 7000 upwards
              are used.

       --udp-flood N
              start N workers that attempt to flood the host with UDP packets to random ports. The IP address of  the
              packets are currently not spoofed. This is only available on systems that support AF_PACKET.

       --udp-flood-domain D
              specify the domain to use, the default is ipv4. Currently ipv4 and ipv6 are supported.

       --udp-flood-if NAME
              use network interface NAME. If the interface NAME does not exist, is not up or does not support the do‐
              main then the loopback (lo) interface is used as the default.

       --udp-flood-ops N
              stop udp-flood stress workers after N bogo operations.

5 送受信のプロセス数を指定する方法(--udp)

--udpオプションは、UDPパケットの送受信プロセス数を指定するためのオプションです。1を指定すると、送受信プロセスがそれぞれ1つずつ生成されます。2を指定すると、送受信プロセスがそれぞれ2つずつ生成されます。あとは同様です。また、-kオプションは、親プロセスと子プロセスを同じ名前にするためのオプションです。psコマンドの実行結果を見ると、全てのプロセスの名前がstress-ngであることがわかります。

[root@server ~]# stress-ng -k --udp 1
stress-ng: info:  [1541] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1541] dispatching hogs: 1 udp

psコマンドを使用してプロセスの状態を確認します。また、後述するstraceコマンドの実行結果から分かるように、PID=1542はUDPパケットの受信プロセス、PID=1543はUDPパケットの送信プロセスです。また、PID=1541は送受信プロセスの終了をwaitシステムコールで待機しています。do_waitはwaitシステムコールの延長として呼び出されるカーネル関数です。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          1541    1515   3  0.0 do_wait
stress-ng          1542    1541   1 43.5 -
stress-ng          1543    1542   3 49.7 -

次に、lsofコマンドを実行します。PID=1542はUDPパケットの受信プロセスです。UDPの7000番ポートで受信待ちしていることがわかります。そして、PID=1543はUDPパケットの送信プロセスです。UDPパケットをポート番号60301から送信します。また、それぞれのプロセスは、任意のIPアドレス(ポート番号の前のIPアドレスワイルドカード(*)なので)でUDPパケットの送受信を実行することがわかります。なお、lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c stress-ng -a -i -a -nP
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
stress-ng 1542 root    4u  IPv4  21463      0t0  UDP *:7000
stress-ng 1543 root    4u  IPv4  23423      0t0  UDP *:60301

PID=1543のプロセスに対して、straceコマンドを実行します。 PID=1543のプロセスはsendtoシステムコールを繰り返し実行しています。宛先UDPポート番号は7000、送信元IPアドレスは任意のIPアドレス(0.0.0.0)であることがわかります。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]#  strace -e trace=network -p 1543
strace: Process 1543 attached
sendto(4, "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"..., 304, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 304
sendto(4, "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"..., 320, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 320
sendto(4, "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"..., 336, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 336
-snip-

PID=1542のプロセスに対して、straceコマンドを実行します。PID=1542のプロセスはrecvfromシステムコールを繰り返し実行しています。送信元UDPポート番号は60301、送信元IPアドレス127.0.0.1であることがわかります。

[root@server ~]#  strace -e trace=network -p 1542
strace: Process 1542 attached
recvfrom(4, "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 960
recvfrom(4, "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 976
recvfrom(4, "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 992
recvfrom(4, "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 1008
-snip-

tcpdumpコマンドを実行すると、loデバイスを介してUDPパケットを送受信していることがわかります。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:01:24.361797 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 86 (480)
20:01:24.361805 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 87 (496)
20:01:24.361813 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 88 (512)
-snip-

Ctrl+cを押下してstress-ngコマンドを終了します。

[root@server ~]# stress-ng -k --udp 1
stress-ng: info:  [1541] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1541] dispatching hogs: 1 udp
^Cstress-ng: info:  [1541] successful run completed in 653.52s (10 mins, 53.52 secs)

6 受信待ちのポート番号を指定する方法(--udp-port)

--udp-port オプションは、UDPパケットの到着を待ち受けるポート番号を指定するためのオプションです。デフォルトではポート番号7000が使用されますが、もし他のプロセスが同じポート番号(7000)をすでに使用している場合、このオプションを使用して、stress-ngプロセスの受信ポート番号を変更することができます。

[root@server ~]# stress-ng -k --udp 1 --udp-port 8000
stress-ng: info:  [1790] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1790] dispatching hogs: 1 udp

lsofコマンドを実行すると、stress-ngプロセスが8000番ポートでUDPパケットの受信待ちをしていることが確認できます。

[root@server ~]# lsof -c stress-ng -a -i -a -nP
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
stress-ng 1791 root    4u  IPv4  25883      0t0  UDP *:8000
stress-ng 1792 root    4u  IPv4  23820      0t0  UDP *:48625

7 IPv4またはIPv6を指定する方法(--udp-domain)

--udp-domainオプションは、UDPパケットの送受信において、IPv4またはIPv6を使用するかを指定するオプションです。デフォルトでは、UDPパケットの送受信にはIPv4が利用されますが、このオプションを使用することでIPv6を選択することが可能です

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行します。

[root@server ~]# tcpdump -i lo -nn

--udp-domainオプションにipv6を指定して、stress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp 1 --udp-domain ipv6
stress-ng: info:  [1314] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1314] dispatching hogs: 1 udp
stress-ng: info:  [1314] successful run completed in 0.01s

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行すると、ループバックアドレスIPv6UDPパケットが送受信されていることが確認できます。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:16:11.211740 IP6 ::1.60769 > ::1.7000:  [|rx] (16)
20:16:11.211915 IP6 ::1.60769 > ::1.7000:  rx type 66 (32)
20:16:11.211938 IP6 ::1.60769 > ::1.7000:  rx type 67 (48)
20:16:11.211958 IP6 ::1.60769 > ::1.7000:  rx type 68 (64)
-snip-

8 実行回数を指定する方法(--udp-ops)

--udp-opsUDPパケットの送信回数を指定するオプションです。

UDPパケットを5回送信してみます。

[root@server ~]# stress-ng -k --udp 1 --udp-ops 5
stress-ng: info:  [1636] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1636] dispatching hogs: 1 udp
stress-ng: info:  [1636] successful run completed in 0.00s

tcpdumpコマンドの実行結果を確認すると、UDPパケットが5回送信されているのがわかります。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:24:55.105270 IP 127.0.0.1.47176 > 127.0.0.1.7000:  [|rx] (16)
20:24:55.105338 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 66 (32)
20:24:55.105361 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 67 (48)
20:24:55.105380 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 68 (64)
20:24:55.105398 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 69 (80)

9 UDPパケット送受信するインタフェースを指定する方法(--udp-if)

--udp-ifオプションは、UDPパケットの送受信に使用するインタフェースを指定するオプションですが、実験をしてみると正常に機能していないようにみえます。

--udp-ifオプションにeth0を指定してstress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp 1 --udp-if eth0 --udp-ops 3
stress-ng: info:  [1235] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1235] dispatching hogs: 1 udp
stress-ng: info:  [1235] successful run completed in 0.01s

tcpdumpコマンドの実行結果を確認すると、インタフェースにeth0を指定したにもかかわらず、lo(127.0.0.1)を使用しています。

[root@server ~]# tcpdump -i any udp port 7000 -nn
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
20:03:14.857652 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  [|rx] (16)
20:03:14.857752 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 66 (32)
20:03:14.857768 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 67 (48)
20:03:14.857779 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 68 (64)
20:03:14.857789 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 69 (80)
-snip-

10 受信待ちのポート番号をランダムに指定する方法(--udp-flood)

--udp-floodオプションは、UDPパケットの受信ポート番号をランダムに指定するオプションです。

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行します。

[root@server ~]# tcpdump -i any udp -nn

stress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp-flood 1 --udp-flood-ops 3
stress-ng: info:  [1273] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1273] dispatching hogs: 1 udp-flood
stress-ng: info:  [1273] successful run completed in 0.00s

tcpdumpコマンドの実行結果を確認すると、UDPパケットの受信ポート番号が1025,31282,1026と変化していることがわかります。

[root@server ~]# tcpdump -i any udp -nn
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
20:14:23.005442 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.1025: UDP, length 1
20:14:23.005498 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.31282: UDP, length 1
20:14:23.005543 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.1026: UDP, length 2

11 UDP-Liteのパケットを送受信する方法(--udp-lite)

私の環境では、CONFIG_UDPLITEが定義されていないので、実験することができませんでした。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

perf topコマンドの使い方

1 perf topコマンドとは?

perfコマンドはLinuxのパフォーマンス分析に使用します。perfコマンドには多くのサブコマンドが含まれており、その中でtopサブコマンドは主にパフォーマンス解析やトラブルシューティングのために利用します。なお、manページではこれらのサブコマンドを「コマンド」と表現していますが、混同を避けるため、この記事では「サブコマンド」と呼ぶことにします。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

3 インストール方法

perfパッケージをインストールします。

[root@server ~]# dnf -y install perf

perfコマンドの版数を確認します。

[root@server ~]# perf -v
perf version 5.14.0-284.30.1.el9_2.x86_64

4 コマンド一覧

perfコマンドには、以下のサブコマンドがあります。

[root@server ~]# perf -h

 usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]

 The most commonly used perf commands are:
   annotate        Read perf.data (created by perf record) and display annotated code
   archive         Create archive with object files with build-ids found in perf.data file
   bench           General framework for benchmark suites
   buildid-cache   Manage build-id cache.
   buildid-list    List the buildids in a perf.data file
   c2c             Shared Data C2C/HITM Analyzer.
   config          Get and set variables in a configuration file.
   daemon          Run record sessions on background
   data            Data file related processing
   diff            Read perf.data files and display the differential profile
   evlist          List the event names in a perf.data file
   ftrace          simple wrapper for kernel's ftrace functionality
   inject          Filter to augment the events stream with additional information
   iostat          Show I/O performance metrics
   kallsyms        Searches running kernel for symbols
   kmem            Tool to trace/measure kernel memory properties
   kvm             Tool to trace/measure kvm guest os
   kwork           Tool to trace/measure kernel work properties (latencies)
   list            List all symbolic event types
   lock            Analyze lock events
   mem             Profile memory accesses
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile
   sched           Tool to trace/measure scheduler properties (latencies)
   script          Read perf.data (created by perf record) and display trace output
   stat            Run a command and gather performance counter statistics
   test            Runs sanity tests.
   timechart       Tool to visualize total system behavior during a workload
   top             System profiling tool.
   version         display the version of perf binary
   probe           Define new dynamic tracepoints
   trace           strace inspired tool

 See 'perf help COMMAND' for more information on a specific command.

各サブコマンド(COMMAND)のヘルプは、以下の書式にしたがって表示します。

perf help COMMAND

topサブコマンドのヘルプを表示してみます。

[root@server ~]# perf help top
PERF-TOP(1)                                          perf Manual                                          PERF-TOP(1)

NAME
       perf-top - System profiling tool.

SYNOPSIS
       perf top [-e <EVENT> | --event=EVENT] [<options>]

DESCRIPTION
       This command generates and displays a performance counter profile in real time.

OPTIONS
       -a, --all-cpus
           System-wide collection. (default)
-snip-

5 テストプログラムの作成

perf topコマンドでCPU使用率等を確認するため、テストプログラムを作成します。

[root@server ~]# vi test.c
[root@server ~]# cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define COUNT 1000000

void func11() {}
void func22() {}

void func1()
{
    for (int i = 0; i < COUNT; i++) {
            func11();
    }
    usleep(1000);  // 1ミリ秒のスリープ
}

void func2()
{
    for (int i = 0; i < COUNT; i++) {
            func22();
    }
    usleep(1000);  // 1ミリ秒のスリープ
}

int main()
{
    srand(time(NULL));
    while (1) {
        int random = rand() % 5; // 0から4の乱数を生成

        if (random == 0) {
            func2(); // 20%の確率でfunc2を呼び出す
        } else {
            func1(); // 80%の確率でfunc1を呼び出す
        }
    }
    return 0; // この部分には到達しない
}

テストプログラムをコンパイルします。

[root@server ~]# gcc -g -Wall -o test test.c

6 基本的な使い方

perfコマンドに続けて、topサブコマンドを指定します。終了するには、Ctrl+cを押下します。

[root@server ~]# perf top

実行結果は以下のようになります。各列の意味は以下のとおりです。

列の名前 意味
Overhead 関数またはプロセスが使用している CPU の割合を表しています
Shared Object プログラムまたはライブラリー名を表しています
Symbol [k]はカーネル空間で実行する関数、[.]はユーザ空間で実行する関数を表しています

tasksetコマンドを使って、テストプログムをCPU0で実行します。なお、tasksetコマンドの使い方は、tasksetコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# taskset -c 0 ./test &
[1] 2713

psコマンドを使用して、プロセスの状態を確認します。testプロセスはCPU0(CPU使用率73%程度)で動作していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C test -o comm,pid,ppid,psr,%cpu
COMMAND             PID    PPID PSR %CPU
test               2713    2587   0 72.7

perf topの実行結果を確認すると、testプログラムの関数が表示されていることがわかります。

7 モニタするCPUを指定する方法(-C)

lscpuコマンドを実行して、マシンに搭載されているCPU数を確認すると、4つあることがわかります。なお、lscpuコマンドの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -ae
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 2:2:2:2          yes
  2    0      2    2 4:4:4:4          yes
  3    0      3    3 6:6:6:6          yes

perf topコマンドを実行します。このとき、CPU0で実行している関数名だけを表示するようにします。

[root@server ~]# perf top -C 0

実行結果を確認すると、CPU0で動作しているtestプロセスが表示されていることがわかります。

次に、CPU1,2,3で実行しているプロセスだけを表示するようにします。

[root@server ~]# perf top -C 1,3

実行結果を確認すると、testプロセスが表示されていないことがわかります。

8 カーネルモードで実行している関数を表示する方法(-U)

-Uオプションは、カーネルモードで実行している関数だけを表示するオプションです。

[root@server ~]# perf top -U

実行結果を確認すると、シンボル列が[K]となっているので、カーネルモードで実行している関数だけが表示されていることがわかります。

9 ユーザモードで実行している関数を方法(-K)

-Kオプションは、ユーザモードで実行している関数だけを表示するオプションです。

[root@server ~]# perf top -K

実行結果を確認すると、シンボル列が[.]となっているので、ユーザモードで実行している関数だけが表示されていることがわかります。

10 表示間隔を変更する方法(-d)

-dオプションは、表示間隔を指定するオプションです。以下のように実行すると、情報が10秒ごとに更新されます。

[root@server ~]# perf top -d 10

11 特定の情報を表示する方法(--sort)

commオプションは、コマンド列を表示するオプションです。

[root@server ~]# perf top --sort comm

実行結果を確認すると、コマンド列が表示されていることがわかります。コマンド列と言っていますが、表示しているのはプロセス名です。

pidオプションは、プロセスID(PID)列を表示するオプションです。commオプションとあわせて指定してみます。

[root@server ~]# perf top --sort comm,pid

実行結果を確認すると、コマンド列とPID列が表示されていることがわかります。testプロセスのPIDが2713であることがわかります。

cpuオプションは、プロセスを実行しているCPU列を表示するオプションです。comm,pidオプションとあわせて指定してみます。

[root@server ~]# perf top --sort comm,pid,cpu

実行結果を確認すると、コマンド列,PID列,CPU列が表示されていることがわかります。testプロセスはCPU0で動作していることがわかります。

12 関数の呼び出し関係を表示する方法(-g)

-gオプションは、関数の呼び出し関係を表示するオプションです。

[root@server ~]# perf top -g

↑↓キーを使って、詳細表示したい関数にカーソルをあわせて、Enterキーを押下します。

次に「Expand」の行にカーソルをあわせて、Enterキーを押下します。

実行結果を確認すると、次のような関数呼び出しになっていることがわかります。また、それぞれの関数のCPU使用率が表示されていることもわかります。
・__libc_start_call_main -> main -> func1
・func1 -> func11

13 perfコマンドをリアルタイム優先度で実行する方法(-r)

-rオプションは、 perfコマンドをリアルタイムプロセスで実行するオプションです。指定しないとタイムシェアリングプロセスとして実行します。リアルタイムプロセスで実行することで、確実にパフォーマンスデータが取得できる一方で、他のプロセスに与える影響が大きくなります。

オプションを指定しないでperf topを実行します。

[root@server ~]# perf top

psコマンドを使用すると、perfプロセスはタイムシェアリングプロセスとして実行されていることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             TS      -

perfコマンド をリアルタイムプロセスの最高優先度(1)で実行します。

[root@server ~]# perf top -r 1

psコマンドを使用すると、perfプロセスはリアルタイムプロセス(優先度1)として実行されていることがわかります。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             FF      1

perfコマンド をリアルタイムプロセスの最低優先度(99)で実行します。

[root@server ~]# perf top -r 99

psコマンドを使用すると、perfプロセスはリアルタイムプロセス(優先度99)として実行されていることがわかります。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             FF     99

sarコマンドの%stealの意味について

1 はじめに

1.1 概要

sarコマンドやvmstatコマンドを使用すると、「%steal」という項目が表示されます。この項目は、仮想マシンのvCPUがホストの物理CPUに割り当てられない割合を表しています。この記事では、意図的にvCPUが物理CPUに割り当てられない状況を作り出し、「%steal」がどのように変化するかを検証してみます。

1.2 出力例

sarコマンドを実行すると、右から2列目に「%steal」が表示されていることがわかります。なお、sarコマンドの使い方は、sarコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# sar 1 -P ALL
19時22分50秒	CPU	%user	%nice	%system	%iowait	%steal	%idle
19時22分51秒	all	0.00	0.00	0.00	0.00	0.00	100.00
19時22分51秒	0	0.00	0.00	0.00	0.00	0.00	100.00
-snip-

vmstatコマンドを実行すると、右端に「st」(%steal)が表示されていることがわかります。なお、vmstatコマンドの使い方は、vmstatコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# vmstat 1 -w
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   0    0            0      3368808         1636       185800    0    0    47     2   44   32   0   1  97   2   0
-snip-

1.3 検証方針

「%steal」の変化を確認するため、以下の手順を実行します。
1. 仮想マシンのvCPU(X)を物理CPU(Y)に固定的に割り当てる。
2. vCPU(X)で実行するプロセスより優先度の高いプロセスを物理CPU(Y)で実行して、vCPU(X)で実行するプロセスの時間を少なくする。

2 検証環境

2.1 版数の確認

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

2.2 搭載CPU数の確認

仮想マシンはCPUを4個搭載しています。なお、lscpuコマンドの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 1:1:1:1          yes
  2    0      2    2 2:2:2:2          yes
  3    0      3    3 3:3:3:3          yes

kvmホストはCPUを8個搭載しています。

[root@kvm ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 2:2:2:2          yes
  2    0      2    2 4:4:4:4          yes
  3    0      3    3 6:6:6:6          yes
  4    0      4    4 8:8:8:8          yes
  5    0      5    5 10:10:10:10      yes
  6    0      6    6 12:12:12:12      yes
  7    0      7    7 14:14:14:14      yes

3 実験1

以下の方針で検証環境を作成します。
1.アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。vCPUと物理CPUを固定的に割り当てることができれば、どのように割り当ててもかまいません。
2.CPU負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

3.1 事前準備

virsh vcpuinfoコマンドを使用すると、vCPU0が物理CPU6に割り当てられていることがわかります。しかし、CPUアフィニティが物理CPU0から物理CPU7に設定されているので、物理CPUの負荷状況によっては、vCPU0が物理CPU6以外に割り当てられる可能性があります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     33.6s
CPU アフィニティー: 0-7/8
-snip

そこで、vCPU0を物理CPU6に固定的に割り当てるため、virsh vcpupinコマンドを使用してvCPU0を物理CPU6に固定します。

[root@kvm ~]# virsh vcpupin 01_server_alma92 --vcpu 0 6

仮想マシンを再起動します。

[root@kvm ~]# virsh reboot 01_server_alma92
ドメイン '01_server_alma92' は再起動されています

virsh vcpuinfoコマンドを実行すると、CPU アフィニティーが物理CPU6となっているので、vCPU0が物理CPU6に固定的に割り当てられていることがわかります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     58.4s
CPU アフィニティー: 6/8
-snip-

3.2 動作検証(kvmホストのCPUに負荷をかける)

stress-ngコマンドを以下の条件で実行します。なお、stress-ngコマンドの使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
・物理CPU6でCPU使用率90%のstress-ngプロセスを実行する。
・stress-ngプロセスは、ラウンドロビン(RR)のスケジューリングポリシーでリアルタイムプロセスとして実行する。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &

psコマンドでstress-ngプロセスの状態を確認します。物理CPU6でCPU使用率90%程度で動作していることがわかります。また。ラウンドロビン(RR)のスケジューリングポリシーで優先度99のリアルタイムプロセスとして動作していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@kvm ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          2476    2209   6  0.0  RR   -     99
stress-ng          2477    2476   6 89.0  RR   -     99

仮想マシンでsarマンドを実行すると、CPU全体(all)の「%steal」が約7%、vCPU0の「%steal」が22%であることがわかります。

[root@server ~]# sar -P ALL 1
22時01分16秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
22時01分17秒     all      0.00      0.00      0.00      0.00      6.76     93.24
22時01分17秒       0      0.00      0.00      0.00      0.00     22.48     77.52
22時01分17秒       1      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       2      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       3      0.00      0.00      0.00      0.00      0.00    100.00
-snip-

さらに、ターミナルを開いてvmstatコマンドを実行してみます。sarコマンドとvmstaコマンドを同時に実行して結果を見比べると、vmstatコマンドの「st」列はsarコマンドのCPU全体の「%steal」に相当することがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3379184         1636       185656    0    0    21     1   23   22   0   0  94   0   5
   0    0            0      3379184         1636       185656    0    0     0     0   73   92   0   0  93   0   7
   0    0            0      3379184         1636       185656    0    0     0     0   56   75   0   0  89   0  11
-snip-

3.3 動作検証(仮想マシンのCPUに負荷をかける)

さらに、kvmホストでもstress-ngプロセスを実行します。kvmホストで優先度の高いstress-ngプロセスを実行すると、仮想マシンで実行するstress-ngプロセスが実行されにくくなります。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &

psコマンドを使用して、stress-ngプロセスの状態を確認します。stress-ngプロセスをCPU使用率90%で起動していますが、仮想マシンで実行しているプロセスよりも優先度の高いプロセスがkvmホストで実行されているので、vCPU0のCPU使用率が約7%であることが確認できます。また、このプロセスがタイムシェアリングプロセス(TS)として実行されていることも確認できます。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          1303    1271   0  0.0  TS   0      -
stress-ng          1304    1303   0  7.4  TS   0      -

sarコマンドを実行します。vCPUでstress-ngプロセスを実行すると、vCPUが物理CPUに割り当てられる割合が少なくなるので、「%steal」の値が大きくなることがわかります。

[root@server ~]# sar -P ALL 1
平均値:      CPU     %user     %nice   %system   %iowait    %steal     %idle
平均値:      all      2.26      0.00      0.25      0.00     22.56     74.94
平均値:        0      9.15      0.00      0.34      0.00     90.51      0.00
平均値:        1      0.00      0.00      0.33      0.00      0.00     99.67
平均値:        2      0.00      0.00      0.33      0.00      0.66     99.01
平均値:        3      0.00      0.00      0.00      0.00      0.33     99.67

vmstat コマンドを実行します。「st」列を確認すると、22%程度(全vCPUの25%)になっていることがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   1    0            0      3330928         1636       208972    0    0     9     0   30   20   1   0  86   0  13
   1    0            0      3330928         1636       208972    0    0     0     0  287   95   2   0  76   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  151   68   2   0  75   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  337  138   2   0  76   0  22

4 実験2

以下の方針で検証環境を作成します。
1. アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。
・vCPU1を物理CPU7に固定的に割り当てる。
2. 物理CPUの負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・物理CPU7でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
3. 仮想CPUの負荷
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。
・vCPU1でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

仮想マシンでstress-ngコマンドを実行します。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &
[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 1 -q &

kvmホストでstress-ngコマンドを実行します。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &
[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 7 --sched rr --sched-prio 99 -q &

sarコマンドを実行すると、vCPU0とvCPU1の「%steal 」の値が約90%となっていて、vCPU0とvCPU1は物理CPUにほとんど割り当てられていないことがわかります。また、allの「%steal 」は約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# sar -P ALL 1
Linux 5.14.0-284.11.1.el9_2.x86_64 (server)     2023年12月02日  _x86_64_        (4 CPU)

20時50分55秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
20時50分56秒     all      4.31      0.00      0.25      0.00     44.92     50.51
20時50分56秒       0      8.25      0.00      0.00      0.00     91.75      0.00
20時50分56秒       1      9.28      0.00      0.00      0.00     90.72      0.00
20時50分56秒       2      0.00      0.00      0.99      0.00      0.00     99.01
20時50分56秒       3      0.00      0.00      0.00      0.00      0.00    100.00

vmstatコマンドを実行します。「st」列の値が約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3361036         1636       193308    0    0    23     1   28   25   0   1  94   0   6
   3    0            0      3361036         1636       193308    0    0     0     0  527  194   4   0  51   0  44
   2    0            0      3361036         1636       193308    0    0     0     0  360  137   5   1  50   0  45
   2    0            0      3361036         1636       193308    0    0     0     0  395  153   5   0  50   0  45
-snip-

5 まとめ

・「%steal」項目は、vCPUが物理CPUに割り当てられなかった割合を表しています。
・vCPUでプロセスを実行していない場合より、vCPUでプロセスを実行している方が、「%steal」の値は大きくなります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

gpgコマンドの使い方

1 gpgコマンドとは?

gpgコマンドは、ファイルの暗号化・復号や署名の作成・検証等を行うコマンドです。

2 検証環境

2.1 ネットワーク構成

gpgコマンドの動作確認をするため、2台の仮想マシン(server ,client)を使用します。そして、X-sanがserver、Y-sanがclientで、それぞれ鍵ペア(公開鍵と秘密鍵)を作成します。

                 192.168.122.0/24
server ------------------------------------- client
      .14                                .87

本記事では、以下のことを確認してみます。
・ファイルの暗号化・復号
・ファイルに署名を作成・署名の検証

2.2 版数

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

私の環境ではpinentryパッケージのインストールが必要だったので、仮想マシンにpinentryパッケージをインストールしました。

[root@server ~]# dnf install pinentry

gpgの版数は以下のとおりです。

[root@server ~]# gpg --version
gpg (GnuPG) 2.3.3
-snip

3 オプション一覧

gpgコマンドのオプションは以下のとおりです。

[root@server ~]# gpg --help
gpg (GnuPG) 2.3.3
libgcrypt 1.10.0-unknown
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /root/.gnupg
サポートしているアルゴリズム:
公開鍵: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
暗号方式: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256,
      TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256
AEAD: EAX, OCB
ハッシュ: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
圧縮: 無圧縮, ZIP, ZLIB, BZIP2

形式: gpg [オプション] [ファイル]
署名、検査、暗号化または復号
デフォルトの操作は、入力データに依存

コマンド:

 -s, --sign                         署名を作成
     --clear-sign                   クリア・テクスト署名を作成
 -b, --detach-sign                  分遣署名を作成
 -e, --encrypt                      データを暗号化
 -c, --symmetric                    暗号化には共通鍵暗号方式のみを使用
 -d, --decrypt                      データを復号 (デフォルト)
     --verify                       署名を検証
 -k, --list-keys                    鍵の一覧
     --list-signatures              鍵と署名の一覧
     --check-signatures             鍵署名の検査と一覧
     --fingerprint                  鍵とフィンガープリントの一覧
 -K, --list-secret-keys             秘密鍵の一覧
     --generate-key                 新しい鍵ペアを生成
     --quick-generate-key           すばやく新しい鍵ペアを生成
     --quick-add-uid                すばやく新しいユーザIDを追加
     --quick-revoke-uid             すばやくユーザIDを失効
     --quick-set-expire             すばやく新しい有効期限を設定
     --full-generate-key            全機能の鍵ペアを生成
     --generate-revocation          失効証明書を生成
     --delete-keys                  公開鍵リングから鍵を削除
     --delete-secret-keys           秘密鍵リングから鍵を削除
     --quick-sign-key               すばやく鍵に署名
     --quick-lsign-key              すばやく鍵へローカルに署名
     --quick-revoke-sig             すばやく鍵への署名を失効
     --sign-key                     鍵に署名
     --lsign-key                    鍵へローカルに署名
     --edit-key                     鍵への署名や編集
     --change-passphrase            パスフレーズの変更
     --export                       鍵をエクスポートする
     --send-keys                    鍵サーバに鍵をエクスポートする
     --receive-keys                 鍵サーバから鍵をインポートする
     --search-keys                  鍵サーバの鍵を検索する
     --refresh-keys                 鍵サーバから鍵を全部更新する
     --import                       鍵のインポート/マージ
     --card-status                  カード・ステイタスを表示
     --edit-card                    カードのデータを変更
     --change-pin                   カードのPINを変更
     --update-trustdb               信用データベースを更新
     --print-md                     メッセージ・ダイジェストを表示
     --server                       サーバ・モードで実行
     --tofu-policy VALUE            TOFUポリシーを鍵に設定する

診断出力を制御するオプション:
 -v, --verbose                      冗長
 -q, --quiet                        いくらかおとなしく
     --options FILE                 FILEからオプションを読み込みます
     --log-file FILE                FILEにサーバ・モードのログを書き出す

コンフィグレーションを制御するオプション:
     --default-key NAME             デフォルトの秘密鍵としてNAMEを用いる
     --encrypt-to NAME              ユーザID NAMEにも暗号化する
     --group SPEC                   電子メールエイリアスを設定する
     --openpgp                      厳密なOpenPGPの振舞を採用
 -n, --dry-run                      無変更
 -i, --interactive                  上書き前に確認

出力を制御するオプション:
 -a, --armor                        ASCII形式の外装を作成
 -o, --output FILE                  出力をFILEに書き出す
     --textmode                     正準テキスト・モードを使用
 -z N                               圧縮レベルをNに設定 (0は非圧縮)

鍵のインポートとエクスポートを制御するオプション:
     --auto-key-locate MECHANISMS   メールアドレスによって鍵を特定する際、MECHANISMSを使用する
     --auto-key-import              署名から手元にない鍵をインポートする
     --include-key-block            署名に公開鍵を含める
     --disable-dirmngr              dirmngrへのすべてのアクセスを無効とする

鍵を指定するオプション:
 -r, --recipient USER-ID            USER-ID用に暗号化
 -u, --local-user USER-ID           署名や復号にこのUSER-IDを使用

Project-Id-Version: gnupg 2.3.0
Report-Msgid-Bugs-To: translations@gnupg.org
PO-Revision-Date: 2021-04-08 10:15+0900
Last-Translator: NIIBE Yutaka <gniibe@fsij.org>
Language-Team: none
Language: ja
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=1; plural=0;
:

(コマンドとオプション全部の一覧は、マニュアル・ページをご覧ください)

例:

 -se -r Bob [ファイル]      ユーザBobへ署名と暗号化
 --clear-sign [ファイル]     クリア・テクスト署名を作成
 --detach-sign [ファイル]   分遣署名を作成
 --list-keys [名前]         鍵を表示
 --fingerprint [名前]       フィンガープリントを表示

バグは <https://bugs.gnupg.org> までご報告ください。

4 基本的な使い方

実践編の前に、gpgコマンドの基本的な使い方を確認してみます。

4.1 鍵ペアを作成する方法(--gen-key)

--gen-keyオプションは、公開鍵と秘密鍵の鍵ペアを作成するオプションです。

[root@server ~]# gpg --gen-key

「本名」はX-san、「電子メールアドレス」はX-san@hoge.comと入力します。そして、「O」を入力して「Enter」キーを押下します。

秘密鍵が盗まれても、悪用されにくくするため、秘密鍵パスフレーズを設定します。パスフレーズを設定したら、「Tab」キーを押下して「OK」にカーソルをあわせます。そして、「Enter」を押下します。

再度、パスフレーズを入力します。

鍵ペアの作成が完了すると、以下のようになります。

4.2 公開鍵を確認する方法( --list-keys、または-k(小文字))

--list-keysオプションは、公開鍵を表示するオプションです。4.1で作成した公開鍵を確認してみます。公開鍵のidがX-sanであることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.3 秘密鍵を確認する方法(--list-secret-keys、または-K(大文字))

--list-secret-keysオプションは、秘密鍵を表示するオプションです。4.1で作成した秘密鍵を確認してみます。

[root@server ~]# gpg --list-secret-keys
/root/.gnupg/pubring.kbx
------------------------
sec   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
ssb   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.4 秘密鍵を削除する方法(--delete-secret-keys)

--delete-secret-keysオプションは、秘密鍵を削除するオプションです。鍵の削除は秘密鍵、公開鍵の順で行わないと、gpgコマンドでエラーが出力されるので、まず、X-sanの秘密鍵から削除します。

[root@server ~]# gpg --delete-secret-keys X-san

「鍵を削除するか?」と聞かれるので、「y」を入力します。

「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。

再度、「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。


秘密鍵を確認すると、リストから削除されたことがわかります。

[root@server ~]# gpg --list-secret-keys
[root@server ~]#

4.5 公開鍵を削除する方法(--delete-keys)

--delete-keysオプションは、公開鍵を削除するオプションです。X-sanの公開鍵を削除します。

[root@server ~]# gpg --delete-keys X-san
gpg (GnuPG) 2.3.3; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa3072/084F72AF5B2AFB76 2023-11-27 X-san <X-san@hoge.com>

この鍵を鍵リングから削除しますか? (y/N) y

リストを確認すると、X-sanの公開鍵が削除されたことがわかります。

[root@server ~]# gpg --list-keys
gpg: 信用データベースの検査
gpg: 究極的に信用する鍵が見つかりません

4.6 鍵をエクスポートする方法(--export)

4.4, 4.5でX-sanの公開鍵、秘密鍵を削除したので、4.1の手順にしたがって、X-sanの公開鍵、秘密鍵を作成しておきます。

--exportオプションは、公開鍵をファイルにエクスポート(書き出す)するオプションです。X-sanの公開鍵をエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

公開鍵を確認すると、X-sanの公開鍵がファイルにエクスポートされたことがわかります。

[root@server ~]# ls X-san.pub
X-san.pub

4.7 鍵をインポートする方法(--import)

clientで作成した公開鍵をserverに送信します。そして、公開鍵のリストにインポートしてみます。

--importオプションは、公開鍵をリストに追加するオプションです。clientでY-sanの公開鍵を作成します。

[root@client ~]# gpg --gen-key

clientで作成したY-sanの公開鍵をエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

serverに公開鍵を送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

serverでY-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートします。

[root@server ~]# gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E: 公開鍵"Y-san <Y-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

公開鍵のリストを確認します。X-sanの公開鍵の他に、Y-sanの公開鍵もリストに追加されていることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

特定の公開鍵だけを表示するには、--list-keysオプションのパラメータにuidを指定します。
たとえば、X-sanだけの公開鍵を表示するには、以下のように実行します。

[root@server ~]# gpg --list-keys X-san
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5 実践編(ファイルを暗号化・復号する方法)

以下の順でファイルを暗号化・復号する方法を確認してみます。
1. serverでY-sanの公開鍵でファイルを暗号化する
2. 暗号化したファイルをserverからclientに送信する
3. clientでY-sanの秘密鍵でファイルを復号する

5.1 事前準備

clientで鍵ペアを作成します。作成した公開鍵を確認します。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

Y-sanの公開鍵をファイルにエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

エクスポートしてファイルをserverに送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

エクスポートしたY-sanの公開鍵はいつでもリストから取り出すことができるので、削除します。

[root@client ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

Y-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートしてリストに追加します。

[root@server ~]#  gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E:"Y-san <Y-san@hoge.com>"変更なし
gpg:           処理数の合計: 1
gpg:               変更なし: 1

Y-sanの公開鍵をリストに追加したので、Y-sanの公開鍵を削除します。

[root@server ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

公開鍵を確認すると、X-sanの公開鍵の他に、Y-sanの公開鍵が追加されたことがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5.2 ファイルを暗号化・復号する方法

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

Y-sanの公開鍵でテスト用ファイルを暗号化します。

[root@server ~]# gpg -r Y-san -ea test.txt
gpg: FBA0E21345620F47: この鍵が本当に本人のものである、という兆候が、ありません

sub  rsa3072/FBA0E21345620F47 2023-11-27 Y-san <Y-san@hoge.com>
  主鍵フィンガープリント: 3F60 5381 2566 75AD C76A  2ECD F120 39A2 064B 868E
  副鍵フィンガープリント: 65FF F682 24BA B68B CF42  3169 FBA0 E213 4562 0F47

この鍵は、このユーザIDをなのる本人のものかどうか確信でき
ません。今から行うことを*本当に*理解していない場合には、
次の質問にはnoと答えてください。

それでもこの鍵を使いますか? (y/N) y

暗号化したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP MESSAGE-----

hQGMA/ug4hNFYg9HAQwAo4TLQiL3dvIucsupKIHZ+PxUrrg4fSxErG0R16ZMtB5R
C/vWll1bgPIiHaDpSgvx0iFNdbJN6PXzDsxqIxBy3LaasdmjamYcPIUF0PvXzWmE
VOH2ZMPJtNDQsySQ78oLov/iiRdGX5WjdF7vWm05HxS1JrYaKSblke9FpPJMii39
NiqRA+zKACIjOYrNZVYXDQGk0kiO8OvIAO2HJ2Qa4Y1XGk/T8Mv6aRfOxnqz6owN
ljG/hFDMilgAeiMT1VtUkwfCll9rKEGzjvAGniknkncBKhu+o/BlGA+1x9Oazfei
Dz6T/MGmslqow7xvInSFOdPFIBLPZEWEvu2+3XydECMMxrbXHL/VFVLAN0nuIzNE
H2oGweYQ0G+bBTpSKL2GYadNrXbV6oa6wEp+ZmjfljjAu9UEGHz+0TMUnNI4oXur
3NojbeUc4PC6my+XMICLZSpjvPA8Cg7iqwUoFdEBw29P+XYhK5wGtUu+kwwn2dqg
dqjsiUhZrP9HeGYNzchy1FgBCQIQdIrpYtGYNGCyzVy2AxZyi67PN9QogNlU0uuG
NP1isCnp581T4oLdU+pYyKAUmnq4mfarNx0SsjB/yq7uiccnW9csBrDHX10Hm82j
IZtqu2r0VByy
=YYzY
-----END PGP MESSAGE-----

暗号化したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

clientで暗号化したファイルを確認します。

[root@client ~]# ls test.txt.asc
test.txt.asc

暗号化したファイルをY-sanの秘密鍵で復号します。-oオプションを使用して、復号した結果をtest.txtに出力します。

[root@client ~]# gpg -d -o test.txt test.txt.asc
gpg: rsa3072鍵, ID FBA0E21345620F47, 日付2023-11-27に暗号化されました
      "Y-san <Y-san@hoge.com>

復号したファイルを確認します。正しく復号できていることがわかります。

[root@client ~]# cat test.txt
0123456789

6 実践編(署名を作成・検証する方法)

以下の順で署名の作成・検証する方法を確認してみます。
1. serverでX-sanの秘密鍵でファイルに署名する
2. 署名したファイルをserverからclientに送信する
3. clientでX-sanの公開鍵で署名を検証する

6.1 事前準備

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

X-sanの秘密鍵で署名したファイルをclientで検証するには、X-sanの公開鍵をclientに送信しておく必要がありますので、X-sanの公開鍵をファイルにエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

エクスポートしたファイルをclientに送信します。

[root@server ~]# scp X-san.pub root@192.168.122.87:/root

X-sanの公開鍵を削除します。

[root@server ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

X-sanの公開鍵をインポートします。

[root@client ~]# gpg --import X-san.pub
gpg: 鍵5098AB5CC2CA9B28: 公開鍵"X-san <X-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

X-sanの公開鍵をリストに追加したので、X-sanの公開鍵を削除しておきます。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

公開鍵のリストを確認します。X-sanの公開鍵がリストに追加されてことがわかります。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  不明  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

X-sanの公開鍵を削除します。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

6.2 署名の作成・検証する方法

X-sanの秘密鍵でテスト用ファイルに署名をします。

[root@server ~]# gpg --clearsign test.txt

署名したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

0123456789
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCAAdFiEE2KxmWPwitkytRtwAUJirXMLKmygFAmVkdNQACgkQUJirXMLK
myi9rgv+OXcaDKzYzB1zMuLeeHiKp+8Cxvx7dNcA57Pso0awgiQlYdRZjJKrbPki
ymasPetS5Y9AQlFfY/Hd3BSw+rd4TI+IwjDBcoCxEI5OrEWvonSVWxD0cZnuy7Ux
1DlzR58xEwlWafA433EKk7i1FmWFeObTMhZjIA0z32d8Asu+XBNZtvDhbB1y4F+f
w26bZVSfYunf3TUICOWNOvvmBrH+RJSuZYuKUJ3Swm+3jcJUyj8OX05pmexqWRwC
5vjCyrVk/KOhm4WGawC+eT1HFiB7/AlUWHOuE/f6mKK0WLXF9Wnya4wlZHGyIAGk
nynAoJSVl0zA+LnHWDs941KN/W7mKNU58XgxfDAC8I4GX5BlzguETCMS3/K8s/dD
bpO8pMsVLPae+InEshDPbnGlRJeQjVhmfB0fz+ChfsdDLaJmC4DQb/Sl7/sHFtU9
FalEKweOJUA1b+rgr1iwYZk2hx8yavie76s3mFqvEbqQPjmd/HWr0c73AsU+kPuP
9tjRCpBG
=tynM
-----END PGP SIGNATURE-----

署名したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

署名を検証します。X-sanが署名したことがわかります。

[root@client ~]# gpg --verify test.txt.asc
gpg: 2023年11月27日 19時52分04秒 JSTに施された署名
gpg:                RSA鍵D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28を使用
gpg: "X-san <X-san@hoge.com>"からの正しい署名 [不明の]
gpg: *警告*: この鍵は信用できる署名で証明されていません!
gpg:       この署名が所有者のものかどうかの検証手段がありません。
 主鍵フィンガープリント: D8AC 6658 FC22 B64C AD46  DC00 5098 AB5C C2CA 9B28
gpg: *警告*: 分遣署名ではありません。ファイル「test.txt」は検証され*ませんでした*!

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

GPGによる公開鍵暗号と署名

Matplotlibの使い方

1 Matplotlibとは?

Matplotlibはグラフを描画するためのライブラリです。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 Matplotlibのインストール方法

グラフを描画するため、Matplotlibライブラリをインストールします。

[root@server ~]# pip install matplotlib

Matplotlibの版数を確認します。

[root@server ~]# pip show matplotlib
Name: matplotlib
Version: 3.8.0
Summary: Python plotting package
Home-page: https://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: PSF
Location: /usr/local/lib64/python3.9/site-packages
Requires: contourpy, cycler, fonttools, importlib-resources, kiwisolver, numpy, packaging, pillow, pyparsing, python-dateutil
Required-by:

Matplotlibで描画するグラフに日本語のタイトル等を表示するため、japanize-matplotlibをインストールします

[root@server ~]# pip install japanize-matplotlib

japanize-matplotlibの版数を確認します。

[root@server ~]# pip show japanize-matplotlib
Name: japanize-matplotlib
Version: 1.1.3
Summary: matplotlibのフォント設定を自動で日本語化する
Home-page: https://github.com/uehara1414/japanize-matplotlib
Author: uehara1414
Author-email: akiya.noface@gmail.com
License: MIT License
Location: /usr/local/lib/python3.9/site-packages
Requires: matplotlib
Required-by:

4 DataFrameからグラフを作成する方法

4.1 グラフの種類

種類 概要
折れ線グラフ 変化をみるときに使う
積み上げグラフ 変化の要因を知りたいときに使う
面グラフ 変化の大きさを強調してみるときに使う
箱ひげグラフ データの散らばり具合を見るときに使う

4.2 テスト用ファイルの作成

テストプログラムが読み込む、テスト用ファイルを作成します。

[root@server ~]# cat test.csv
名前,国語,数学,英語
佐藤,90,50,80
鈴木,50,100,80
田中,70,60,80
伊藤,40,60,100

4.3 折れ線グラフを作成する方法(plot)

plotメソッドは、折れ線グラフを作成するためのメソッドです。

折れ線グラフを作成するテストプログラムを作成します。importステートメントの意味は以下のとおりです。
・matplotlibライブラリからグラフ描画(pyplot)に関する機能をpltとしてインポートする。
・日本語タイトルなどを描画するために使用するjapanize_matplotlibをインポートする。
・データ分析のために使用するpandasライブラリをpdとしてインポートする。
なお、pandasの使い方は、pandasの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 折れ線グラフを作成
df.plot()

# グラフを表示
plt.show()

テストプログラムを実行すると、テスト用ファイルのデータが折れ線グラフで表示されていることがわかります。なお、本記事では、グラフを表示するために、MobaXtermを使用しています。MobaXtermの使い方は、MobaXtermの使い方 - hana_shinのLinux技術ブログを参照してください。

4.4 棒グラフを作成する方法(plot.bar)

棒グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# グラフを表示
plt.show()

テストプログラムを実行すると、棒グラフが表示されていることがわかります。

4.5 積み上げグラフを作成する方法(plot.bar(stacked=true))

積み上げグラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 積み上げグラフを作成
df.plot.bar(stacked=True)

# グラフを表示
plt.show()

テストプログラムを実行すると、積み上げグラフが表示されていることがわかります。

4.6 面グラフを作成する方法(plot.area)

面グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 面グラフを作成
df.plot.area()

# グラフを表示
plt.show()

テストプログラムを実行すると、面グラフが表示されていることがわかります。

4.7 箱ひげグラフを作成する方法(plot.box)

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 箱ひげグラフを作成
df.plot.box()

# グラフを表示
plt.show()

テストプログラムを実行すると、箱ひげグラフが表示されていることがわかります。

5 グラフに汎用要素を設定する方法

5.1 タイトル、ラベルを表示する方法

4.4で作成した棒グラフに、タイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、グラフのタイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルが表示されていることがわかります。

5.2 X軸のインデックスを表示する方法

X軸のインデックスに名前を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、X軸に名前が表示されていることがわかります。

5.3 グリッドをオンにする方法

グリッドを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、グリッドが表示されていることがわかります。

6 個別のデータをグラフで表示する方法

これまでは読み込んだ全てのデータをグラフに表示していました。ここでは、特定の項目(例えば国語)だけのグラフの表示や、ある人物(例えば佐藤さん)だけのグラフを表示してみます。

6.1 特定の項目(数学と国語)のグラフを表示する方法

各個人の数学と国語に関する点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df[["国語","数学"]].plot.barh()

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、各個人の数学、国語の点数が表示されていることがわかります。

6.2 特定の項目(佐藤さん)の棒グラフを表示する方法

佐藤さんの英語、数学、国語の点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df.loc["佐藤"].plot.barh()

# タイトルを追加
plt.title("佐藤さんの成績")

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が棒グラフで表示されていることがわかります。

6.3 特定の項目(佐藤さん)の円グラフを表示する方法

佐藤さんの英語、数学、国語の点数を円グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 円グラフを作成する
df.loc["佐藤"].plot.pie()

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が円グラフで表示されていることがわかります

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

記事作成に参考にした書籍です。

サンプルコードが短く、分かりやすいです。
Pythonコードレシピ集(単行本)


Pythonコードレシピ集(Kindle版)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)

Webのスクレイピングについて簡潔で分かりやすく説明されています。

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(単行本)

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(Kindle版)

  • Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ


Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ(単行本)

Python 1年生 体験してわかる!会話でまなべる!プログラミングのしくみ(Kindle版)

pandasの使い方

1 はじめに

pandasとは、データ分析用ライブラリです。pandasで利用できるデータは、表計算ソフトExcelで扱うような表形式のデータです。pandasを使うと、Excelなどのファイルから表形式のデータを読み込み、集計、データ抽出、グラフの表示などが行えます。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 事前準備

スシの名前、価格、注文数を記録したcsvファイルを作成します。なお、ikaの値段は意図的に空欄にしています。

[root@server ~]# cat sushi.csv
sushi,price,orders
ikura,500,2
aji,400,4
uni,700,2
toro,800,4
ika,,2
saba,200,2
kohada,200,2

4 csvファイルに対する操作

4.1 csvファイルを読み込む方法(read_csv)

csvファイルを読み込むためには、read_csvメソッドを使用します。read_csvメソッドの引数には、csvファイルのパスを指定します。

読み込んだファイル(sushi.csv)を表示するテストプログラムを作成します。なお、テストプログラム中のdfはDataFrameを表しています。Pandasには2つの主要なデータ構造があって、Series(シリーズ)が1次元のデータ、DataFrame(データフレーム)が2次元のデータに対応しています。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df)

テストプログラムを実行すると、sushi.csvの中身が表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3    toro  800.0       4
4     ika    NaN       2
5    saba  200.0       2
6  kohada  200.0       2

4.2 csvファイルに書き込む方法(to_csv)

読み込んだファイル(sushi.csv)を/tmp直下に書き込むテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df.to_csv('/tmp/sushi.csv')

テストプログラムを実行します。

[root@server ~]# ./test.py

/tmpにファイルが書き込まれたことがわかります。

[root@server ~]# cat /tmp/sushi.csv
,sushi,price,orders
0,ikura,500.0,2
1,aji,400.0,4
2,uni,700.0,2
3,toro,800.0,4
4,ika,,2
5,saba,200.0,2
6,kohada,200.0,2

次は、指定した列を/tmp直下のファイル(test.csv)に保存してみます。ここでは、sushi列のみをファイルに保存するテストぽプログラムを作成してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df.to_csv('/tmp/sushi.csv', columns=['sushi'])

テストプログラムを実行します。

[root@server ~]# ./test.py

テストプログラムを実行すると、sushi列がファイルに保存するされていることがわかります。

[root@server ~]# cat /tmp/sushi.csv
,sushi
0,ikura
1,aji
2,uni
3,toro
4,ika
5,saba
6,kohada

あと始末をします。

[root@server ~]# rm /tmp/sushi.csv

5 DataFrameの各種情報を取得する方法

5.1 DataFrameの要約情報を取得する方法(info)

infoメソッドは、DataFrameのデータ型やメモリ使用量などを表示するメソッドです。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.info())

テストプログラムを実行すると、DataFrameのデータ型やメモリ使用量が表示されていることがわかります。

[root@server ~]# ./test.py
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7 entries, 0 to 6
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   sushi   7 non-null      object
 1   price   6 non-null      float64
 2   orders  7 non-null      int64
dtypes: float64(1), int64(1), object(1)
memory usage: 296.0+ bytes
None

5.2 DataFrameの行数を求める方法(len)

len はPythonの組み込み関数であり、DataFrameの行数を返します。この関数には、シーケンス(文字列、バイト列、タプル、リスト、rangeなど)またはコレクション(辞書、集合、凍結集合など)を引数として渡します。

DataFrameの行数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(len(df))

テストプログラムを実行すると、DataFrameの行数が7行であることがわかります。

[root@server ~]# ./test.py
7

5.3 DataFrameの列名を取得する方法(columns)

columnsはDataFrame の属性です。columns属性は、DataFrame内の列名を含むリストを返します。

CSVファイルからデータを読み込み、DataFrame内の列名を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.columns)

テストプログラムを実行すると、列名を含むリストが表示されていることがわかります。

[root@server ~]# ./test.py
Index(['sushi', 'price', 'orders'], dtype='object')

次は、DataFrameのカラム数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(len(df.columns))

テストプログラムを実行すると、DataFrameのカラム数が3であることがわかります。

[root@server ~]# ./test.py
3

5.4 行数・列数を取得する方法(shape)

shape はpandasのDataFrameの属性です。DataFrameのshape 属性は、行数と列数を含むタプルを返します。このタプルの最初の要素は行数(行の数)であり、2番目の要素は列数(列の数)です。

CSVファイルからデータを読み込み、そのデータの行数と列数を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.shape)
print(df.shape[0])
print(df.shape[1])

テストプログラムを実行すると、読み込んだファイルの行数と列数が表示されていることがわかります。

[root@server ~]# ./test.py
(7, 3)
7
3

6 DataFrameの行データを取得する方法

6.1 先頭から指定した行数を取り出す方法(head)

headはDataFrameの最初のいくつかの行を取得するたのメソッドです。headに引数を指定しないと、DataFrameの最初の5行を取得します。

DataFrameの先頭2行を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.head(2))

テストプログラムを実行すると、DataFrameの先頭2行が表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2
1    aji  400.0       4

6.2 末尾から指定した行数を取り出す方法(tail)

headはDataFrameの最後のいくつかの行を取得するたのメソッドです。

DataFrameの末尾1行を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.tail(1))

テストプログラムを実行すると、DataFrameの末尾1行が表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
6  kohada  200.0       2

6.3 DataFrameの指定した行をまとめて取り出す方法(iloc)

DataFrameのindexが2から4までの行を取り出してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.iloc[2:4])

テストプログラムを実行すると、DataFrameの2行目から4行目までの行が表示されていることがわかります。

[root@server ~]# ./test.py
  sushi  price  orders
2   uni  700.0       2
3  toro  800.0       4

7 DataFrameの列データを取得する方法

DataFrameのsushi列を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df['sushi'])

テストプログラムを実行すると、DataFrameのsushi列が表示されていることがわかります。

[root@server ~]# ./test.py
0     ikura
1       aji
2       uni
3      toro
4       ika
5      saba
6    kohada
Name: sushi, dtype: object

DataFrameのprice列を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df['price'])

テストプログラムを実行すると、DataFrameのprice列が表示されていることがわかります。なお、ikaの価格は未設定なので、実行結果はNaNと表示されています。

[root@server ~]# ./test.py
0    500.0
1    400.0
2    700.0
3    800.0
4      NaN
5    200.0
6    200.0
Name: price, dtype: float64

8 DataFrameの基本統計量を取得する方法

8.1 列の最大値の取り出し方(max)

maxは、最大値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(price列)の最大値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.max())

テストプログラムを実行すると、価格の最大値は800円であることがわかります。

[root@server ~]# ./test.py
800.0

8.2 列の最小値の取り出し方(min)

minは、最小値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(price列)の最小値を表示するテストプログラムを作成します。

200.0
[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.min())

テストプログラムを実行すると、価格の最小値が200円であることがわかります。

[root@server ~]# ./test.py
200.0

8.3 平均値求める方法(mean)

meanは、平均値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(preice列)の平均値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.mean())

テストプログラムを実行すると、価格の平均値が467円であることがわかります。

[root@server ~]# ./test.py
466.6666666666667
[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
mean_price = (df['price']).mean()
mean_orders = (df['orders']).mean()

print("平均 price:", mean_price)
print("平均 orders:", mean_orders)
[root@server ~]# ./test.py
平均 price: 466.6666666666667
平均 orders: 2.5714285714285716

8.4 ユニークな値を調べる方法(unique)

uniqueは、Series内のユニークな値(重複のない値)を抽出するメソッドです。

CSVファイルから寿司の価格データを読み込み、ユニークな値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.price.unique())

テストプログラムを実行すると、200円のスシはsabaとkohadaの2つありますが、1つにまとめられていることがわかります。

[root@server ~]# ./test.py
[500. 400. 700. 800.  nan 200.]

9 フィルタリングする方法

9.1 比較演算子で行を抽出する方法(query)

queryは条件を指定してDataFrameからデータを抽出するためのメソッドです。

price列が300円未満のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.query('price < 300'))

テストプログラムを実行すると、300円未満のスシが表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2

次に、price列が400円以上のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.query('not price < 400'))

テストプログラムを実行すると、400円以上のスシが表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2
1    aji  400.0       4
2    uni  700.0       2
3   toro  800.0       4
4    ika    NaN       2

9.2 完全一致によるフィルタリング方法

sushi列がikuraの行を抽出するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
raw = (df.sushi == 'ikura')
print(df[raw])

テストプログラムを実行すると、ikuraの行が表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2

price列が200円のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
raw = (df.price == 200)
print(df[raw])

テストプログラムを実行すると、price列が200円のスシが表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2

10 DataFrameをソートする方法

10.1 昇順にソートする方法

DataFrameのprice列を昇順にソートテストプログラムを作成します。デフォルトは昇順でソートします。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('price')

テストプログラムを実行すると、price列が昇順に表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2
1     aji  400.0       4
0   ikura  500.0       2
2     uni  700.0       2
3    toro  800.0       4
4     ika    NaN       2

10.2 降順にソートする方法

DataFrameの価格(price)を降順にソートするテストプログラムを作成します。ascending=Falseを指定すると、降順にソートすることができます

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('price', ascending=False))

テストプログラムを実行すると、価格が降順になっていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
3    toro  800.0       4
2     uni  700.0       2
0   ikura  500.0       2
1     aji  400.0       4
5    saba  200.0       2
6  kohada  200.0       2
4     ika    NaN       2

次に、DataFrameのsushi列を降順に並べるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('sushi', ascending=False))

テストプログラムを実行すると、sushi列がz->aの順で表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
2     uni  700.0       2
3    toro  800.0       4
5    saba  200.0       2
6  kohada  200.0       2
0   ikura  500.0       2
4     ika    NaN       2
1     aji  400.0       4

11 DataFrameの値を置換する方法(replace)

replaceはSeriesやDataFrame内の値を指定した値で置換するためのメソッドです。

DataFrameのsushi列のtoroをohtoroに置換するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.replace('toro', 'ohtoro'))

テストプログラムを実行すると、toroがohtoroに置換されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3  ohtoro  800.0       4
4     ika    NaN       2
5    saba  200.0       2
6  kohada  200.0       2

12 行列の名前を変更する方法(rename)

renameは、DataFrameの列名や行ラベルを変更するためのメソッドです。

12.1 列の名前を変更する方法

DataFrameの列名をpriceからkakakに変更するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df_new = df.rename(columns={'price':'kakaku'})
print(df_new)

テストプログラムを実行すると、priceがkakakuに変更されたことがわかります。

[root@server ~]# ./test.py
    sushi  kakaku  orders
0   ikura   500.0       2
1     aji   400.0       4
2     uni   700.0       2
3    toro   800.0       4
4     ika     NaN       2
5    saba   200.0       2
6  kohada   200.0       2

次に、DataFrameの2つの列名、priceをkakaku、ordersをcyumonに変更してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df_new = df.rename(columns={'price':'kakaku','orders':'cyumon'})
print(df_new)

テストプログラムを実行すると、priceがkakaku、ordersがcyumonに変更されたことがわかります。

[root@server ~]# ./test.py
    sushi  kakaku  cyumon
0   ikura   500.0       2
1     aji   400.0       4
2     uni   700.0       2
3    toro   800.0       4
4     ika     NaN       2
5    saba   200.0       2
6  kohada   200.0       2

13 欠損値の扱い方

欠損値(NaN)の扱うメソッドに、isnull,dropna,fillna,notnullがあります。各メソッドについて動作確認をしてみます。

13.1 欠損値を判定する方法(isnull)

isnullは、各要素に対して判定を行い、欠損値(NaN)であればTrue、欠損値でなければFalseと判定します。

要素が欠損値ならTrue、欠損値でなければFalseを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.isnull())

テストプログラムを実行すると、ikaの価格は欠損しているため、Trueと表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  False  False   False
1  False  False   False
2  False  False   False
3  False  False   False
4  False   True   False
5  False  False   False
6  False  False   False

13.2 欠損値を削除する方法(dropna)

dropnaは、指定した条件に合致するデータ列に欠損値(NaN)が存在するかどうかをチェックし、条件を満たす場合にそのデータ列を削除するメソッドです。

欠損値(NaN)が存在するデータ列を削除するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.dropna())

テストプログラムを実行すると、欠損値が含まれるikaの行が削除されたことがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3    toro  800.0       4
5    saba  200.0       2
6  kohada  200.0       2

13.3 列の欠損値を補完する方法(fillna)

fillnaは欠損値を任意の値で穴埋めするメソッドです。ここでは、ikaの価格(欠損値)を平均値で補完してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
mean = series.mean()
filled = series.fillna(mean)
print(filled)

テストプログラムを実行すると、ikaの価格が平均値(466.666667)で補完されていることがわかります。

[root@server ~]# ./test.py
0    500.000000
1    400.000000
2    700.000000
3    800.000000
4    466.666667
5    200.000000
6    200.000000
Name: price, dtype: float64

14 日付に関する操作方法

テストプログラムが読み込みファイルを作成します。

[root@server ~]# cat date.csv
id,date,order
01,2021-08-01,3
02,2021-08-10,2
03,2021-09-10,1
04,2021-10-11,4
04,2021-10-20,5

14.1 object型からdatetime64型に変換する方法(to_datetime)

to_datetimeは日付と時刻のデータをdatetimeオブジェクトに変換するためのメソッドです。

date列の型を確認するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
print(df.dtypes)

date列はobject型であることがわかります。

[root@server ~]# ./test.py
id        int64
date     object
order     int64
dtype: object

次に、object型からdatetime64型に変換してみます。datetime64型に変換することで、例えば年だけを取得したい、月だけを取得したいということができます

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('date.csv')
df["date"] = pd.to_datetime(df["date"])
print(df.dtypes)

date列がobject型からdatetime64型に変換されたことがわかります。

[root@server ~]# ./test.py
id                int64
date     datetime64[ns]
order             int64
dtype: object

14.2 列を任意のフォーマットに一括変換する方法(strftime)

dt.strftimeは、列を任意のフォーマットの文字列に一括変換する関数です。%mで月のフォーマットを指定しています。年月の場合は%Y%mと指定します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
df['date']=pd.to_datetime(df['date'])
df['month']=df['date'].dt.strftime('%m')
print(df)

年月日から月に変換されたmonth列が追加されたことがわかります。

[root@server ~]# ./test.py
   id       date  order month
0   1 2021-08-01      3    08
1   2 2021-08-10      2    08
2   3 2021-09-10      1    09
3   4 2021-10-11      4    10
4   4 2021-10-20      5    10

14.3 グルーピングする方法(groupby)

データをグルーピングして、グループごとにデータを集約して、それぞれの合計/最大/最小等の統計量を算出したりすることができます。

まず、注文数について、月単位の合計数を求めてみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('date.csv')
df['date'] = pd.to_datetime(df['date'])
df['month'] = df['date'].dt.strftime('%Y-%m')

monthly_orders = df.groupby('month')['order'].sum()

print(monthly_orders)

注文数が、8月は5、9月は1、10月は9であることがわかります。

[root@server ~]# ./test.py
month
2021-08    5
2021-09    1
2021-10    9
Name: order, dtype: int64

次に、各月の一番多い注文数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
df['date']=pd.to_datetime(df['date'])
df['month']=df['date'].dt.strftime('%m')
print(df.groupby('month').max()['order'])

各月の注文の最大数は、8月は3、9月は1、10月は5であることがわかります。

[root@server ~]# ./test.py
month
08    3
09    1
10    5
Name: order, dtype: int64

15 DataFrameを結合する方法(merge)

mergeはpandasライブラリのデータ結合操作を行うためのメソッドです。mergeメソッドを使用することで、異なるDataFrameオブジェクトのデータを共通のキー(列)に基づいて結合し、新しいDataFrameを生成できます。

名前と国語の点数を記録したkokugo.csvを作成します。

[root@server ~]# cat kokugo.csv
name,kokugo
kato,70
suzuki,80
tanaka,60

名前と算数の点数を記録したsansu.csvを作成します。

[root@server ~]# cat sansu.csv
name,sansu
kato,50
suzuki,70
tanaka,90

kokugo.csvとsansu.csvを結合するテストプログラムを作成します。共通する列をonに指定することで、紐づく値を軸に結合することができます。howでkokugoとsansuのどちらを主軸にするかを指定します。今回は、kokugoにsansuを結合するので、how='left'と指定します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

kokugo = pd.read_csv('kokugo.csv')
sansu = pd.read_csv('sansu.csv')
print(pd.merge(kokugo,sansu, on='name', how='left'))

kokugo.csvとsansu.csvに共通のキー(nama)で結合されていることがわかります。

[root@server ~]# ./test.py
     name  kokugo  sansu
0    kato      70     50
1  suzuki      80     70
2  tanaka      60     90

16 その他

16.1 複数ファイルを読み込む方法

テストプログラムが読み込むファイルを作成します。

[root@server ~]# cat data1.csv
name,age
suzuki,40
kato,50

テストプログラムが読み込むファイルを作成します。

[root@server ~]# cat data2.csv
name,age
tanaka,30
itakahashi,20

テストプログラムを作成します。
globはPythonの組み込みライブラリです。指定したパターンに一致するファイルやディレクトリのリストを取得するために使用します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import glob

all_files=glob.glob('/root/data[0-9].csv')

li = []
for filename in all_files:
  df = pd.read_csv(filename, delimiter=",")
  li.append(df)
frame = pd.concat(li, ignore_index=True)
print(frame)

テストプログラムを実行すると、

[root@server ~]# ./test.py
         name  age
0      tanaka   30
1  itakahashi   20
2      suzuki   40
3        kato   50

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

記事作成に参考にした書籍です。

サンプルコードが短く、分かりやすいです。

Pythonコードレシピ集(Kindle版)

Pythonコードレシピ集(単行本)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)