File Coverage

blib/lib/Baseball/Sabermetrics.pm
Criterion Covered Total %
statement 78 96 81.2
branch 7 20 35.0
condition 1 9 11.1
subroutine 13 14 92.8
pod 3 8 37.5
total 102 147 69.3


line stmt bran cond sub pod time code
1             package Baseball::Sabermetrics;
2              
3 2     2   55214 use Baseball::Sabermetrics::Team;
  2         7  
  2         61  
4 2     2   2014 use Baseball::Sabermetrics::Player;
  2         17  
  2         57  
5 2     2   12 use strict;
  2         4  
  2         116  
6 2     2   11 use warnings;
  2         4  
  2         70  
7 2     2   11 use base qw/ Baseball::Sabermetrics::Team /;
  2         4  
  2         1351  
8              
9             =head1 NAME
10              
11             Baseball::Sabermetrics - A Baseball Statistics Module
12              
13             =cut
14              
15             our $VERSION = '0.03';
16              
17             =head1 SYNOPSIS
18              
19             Baseball::Sabermetrics provides an easy interface for calculating baseball statistics, given a data importer. In this package, I've written CPBL.pm for (I, L).
20              
21             use Baseball::Sabermetrics;
22             use Baseball::Sabermetrics::CPBL;
23              
24             my $league = Baseball::Sabermetrics->new(league => 'CPBL');
25              
26             # Actually these are predefined.
27             # Those data with 'p_' or '_allowed' here are for seperating pitchers
28             # and batters.
29              
30             $league->define(
31             rc => 'ab * obp',
32             babip => '(h_allowed - hr_allowed) / (p_pa - h_allowed - p_so - p_bb - hr_allowed',
33             # what started with '$' will be reserved.
34             # Players have team and league predefined, and team has league.
35             formula1 => 'hr / $_->team->hr';
36             formula2 => 'hr / $_->league->hr';
37             complex => sub {
38             print "You can write a sub directly\n";
39             $_->slg - $_->ba;
40             },
41             ...
42             );
43              
44             # Some formulas can be applied to players, teams, and league, depend on what
45             # columns are used in the formula. For example, ab and obp are defined for
46             # players, teams, and league, so that rc is available for all of them.
47              
48             # top 5 obp of teams
49             $_->print qw/ team name ba obp slg isop / for $league->top('teams', 5, 'obp');
50              
51             # top 10 obp of players
52             $_->print qw/ team name ba obp slg isop / for $league->top('players', 10, 'obp');
53              
54             # show a player's information
55             $league->players('Chien-Ming Wang')->print qw/ win lose ip so bb whip go_ao /;
56             $league->teams('Yankees')->players('Chien-Ming Wang')->print qw/ win lose ip so bb whip go_ao /;
57              
58             # show team statistics data (accumulated from players')
59             $league->{Yankees}->print qw/ win lose ip so bb whip go_ao /;
60              
61             # give a brief report for pitchers/batters of the team
62             $league->{Yankees}->report_pitchers qw/ name ip p_so p_bb whip go_ab /;
63             $league->{Yankees}->report_batters qw/ name ba obp slg isop /;
64              
65             $league->report_teams qw/ name win lose era obp /;
66              
67             # show all available formula
68             print join ' ', $league->formula_list;
69              
70             =head1 Data Structure
71              
72             Baseball::Sabermetrics is aimed for providing a base class of your interested teams (a league, for example). You'll need to provide a data retriever to pull data out. The following example shows how you have to fill data into this structure.
73              
74             $league = {
75             teams => {
76             Yankees => {
77             players => {
78             "Chien-Ming Wang" => {
79             bio => {
80             bats => 'right', # coule be left, switch
81             throws => 'right',
82             },
83             ip => 57.33333333333,
84             game => 9,
85             ...
86             fielding => {
87             p => {
88             tc => 43,
89             pop => 4,
90             ...
91             },
92             # b1 => { }, b2 => { }, b3 => { },
93             # first, second and thrid baseman should be
94             # b1, b2, and b3 respectively for convenient in
95             # fielding context. Because the initial of the
96             # name of subroutine can't be a number in perl.
97             },
98             };
99             ...
100             }
101             },
102             Athletics => {
103             ...
104             },
105             },
106             };
107              
108             =head1 TERMS
109              
110             Available terms of players (including teams and league, which are accumulated from players and could be treated as an abstract player) are:
111              
112             # pitching
113             p_game win lose tie gs sv bs hld cg sho ip p_pa np h_allowed
114             hr_allowed sh_allowed sf_allowed p_bb p_ibb hb p_so wp bk ra er
115              
116             # batting
117             pa ab rbi r h 1b 2b 3b hr tb dp sh sf ibb bb hbp so sb cs
118             tc po a e f_dp ppo tp pb c_cs c_sb
119              
120             # fielding
121             pos fgame tc po a e f_dp tp pb c_cs c_sb
122              
123             And there are additional terms for team:
124              
125             game win lose tie
126              
127              
128             =head1 FUNCTIONS
129              
130             =over 4
131              
132             =item new([I<%hash>])
133              
134             Create sabermetric data set of a group of teams. The following keys are supported:
135              
136             =over 8
137              
138             league:
139             a string like 'CPBL', which is a module and has to be defined in Baseball::Sabermetrics::League::CPBL.
140              
141             data:
142             If your league is not exists there, you can feed in a structure mentioned above.
143              
144             Accumulate:
145             If $hash{Accumulate} is false, players data will not be accumulated to their teams and the league (and therefore team-wise and league-wise statistics are not allowed). Default is to accumulate stats.
146              
147             =back
148              
149             =cut
150              
151             sub new
152             {
153 1     1 1 69 my ($class, %config) = @_;
154 1         2 my $self;
155            
156 1 50       3 if (exists $config{data}) {
    0          
157 1         2 $self = $config{data};
158             }
159             elsif (exists $config{league}) {
160 0         0 eval "require Baseball::Sabermetrics::League::$config{league}; \$self = Baseball::Sabermetrics::League::$config{league}->new(\%config);";
161 0 0       0 die unless $self;
162             }
163             else {
164 0         0 die "You have to provide statistic data";
165             }
166              
167 1         2 bless $self, $class;
168              
169 1 50 33     5 if (exists $config{Accumulate} && !$config{Accumulate}) {
170 0         0 $self->{_DontAccumulate} = 1;
171             }
172              
173 1         1 for my $team (values %{$self->{teams}}) {
  1         13  
174 3         19 $team = Baseball::Sabermetrics::Team->new($team);
175 3         12 $team->{league} = $self;
176 3         5 for my $p (values %{$team->{players}}) {
  3         7  
177 7         28 $p = Baseball::Sabermetrics::Player->new($p);
178 7         15 $p->{team} = $team;
179 7         33 $p->{league} = $self;
180             }
181             }
182              
183 1         6 setup_common_info($self);
184              
185 1         5 return $self;
186             }
187              
188             sub player_accumulate_term
189             {
190             # picher and batter's game is the same here, could be a problem later?
191 7     7 0 44 return qw/ p_game win lose tie gs sv bs hld cg sho ip p_pa np h_allowed
192             hr_allowed sh_allowed sf_allowed p_bb p_ibb hb p_so wp bk ra er
193             pa ab rbi r h 1b 2b 3b hr tb dp sh sf ibb bb hbp so sb cs
194             tc po a e f_dp ppo tp pb c_cs c_sb /;
195             }
196              
197             sub fielding_accumulate_term
198             {
199 9     9 0 23 return qw/ pos fgame tc po a e f_dp tp pb c_cs c_sb /;
200             }
201              
202             sub team_accumulate_term
203             {
204 3     3 0 6 return qw/ game win lose tie /;
205             }
206              
207             sub setup_common_info
208             {
209 1     1 0 2 my $league = shift;
210 1         4 $league->{fielding} = Baseball::Sabermetrics::Team->new();
211 1         3 for (qw/ p c b1 b2 b3 ss lf cf rf of /) {
212 10         26 $league->{fielding}->{$_} = Baseball::Sabermetrics::Player->new();
213             }
214              
215 1         4 for my $tname (keys %{$league->{teams}}) {
  1         5  
216 3         5 my $team = $league->{teams}->{$tname};
217 3         5 $team->{name} = $tname;
218 3 50       11 unless (exists $league->{_DontAccumulate}) {
219 3         10 $team->{fielding} = Baseball::Sabermetrics::Team->new();
220 3         6 for (qw/ p c b1 b2 b3 ss lf cf rf of /) {
221 30         81 $team->{fielding}->{$_} = Baseball::Sabermetrics::Player->new();
222             }
223              
224 2     2   13 no warnings; # FIXME
  2         15  
  2         1934  
225 3         5 for my $name (keys %{$team->{players}}) {
  3         6  
226 7         13 my $p = $team->{players}->{$name};
227 7         11 $p->{name} = $name;
228 7         15 for (player_accumulate_term()) {
229 378         510 $league->{$_} += $p->{$_};
230 378         594 $team->{$_} += $p->{$_};
231             }
232             }
233 3         6 for my $p (values %{$team->{players}}) {
  3         9  
234 7         6 for my $pos (keys %{$p->{fielding}}) {
  7         23  
235 0         0 for (fielding_accumulate_term()) {
236 0         0 $team->{fielding}->{$pos}->{$_} += $p->{fielding}->{$pos}->{$_};
237 0         0 $league->{fielding}->{$pos}->{$_} += $p->{fielding}->{$pos}->{$_};
238             }
239              
240 0 0 0     0 if ($pos eq 'lf' or $pos eq 'cf' or $pos eq 'rf') {
      0        
241 0         0 for (fielding_accumulate_term()) {
242 0         0 $p->{fielding}->{of}->{$_} += $p->{fielding}->{$pos}->{$_};
243             }
244             }
245             }
246             }
247              
248 3         5 for my $pos (qw/ lf cf rf /) {
249 9         17 for (fielding_accumulate_term()) {
250 99         176 $league->{fielding}->{of}->{$_} += $team->{fielding}->{$pos}->{$_};
251 99         197 $team->{fielding}->{of}->{$_} += $team->{fielding}->{$pos}->{$_};
252             }
253             }
254              
255 3         7 for (team_accumulate_term()) {
256 12         23 $league->{$_} += $team->{$_};
257             }
258             }
259 3         4 $league->{players}->{$_} = $team->{players}->{$_} for (keys %{$team->{players}});
  3         20  
260             }
261 1         6 delete $league->{_DontAccumulate};
262             }
263              
264             =item players([$name])
265              
266             for ($league->players) { ... }
267             # or specify the name for the player
268             print $league->players('Someone')->obp;
269              
270             =cut
271              
272             sub players
273             {
274 2     2 1 16 my ($self, $name) = @_;
275 2 50       8 if ($name) {
276 2 50       9 die "Player not found: $name\n" unless exists $self->{players}->{$name};
277 2         8 return $self->{players}->{$name};
278             }
279 0         0 return values %{$self->{players}};
  0         0  
280             }
281              
282             =item teams([$name])
283              
284             for ($league->teams) { ... }
285             # or specify the name for the team
286             print $league->teams('Someone')->win;
287              
288             =cut
289              
290             sub teams
291             {
292 1     1 1 604 my ($self, $name) = @_;
293 1 50       8 if ($name) {
294 1 50       7 die "Team not found: $name\n" unless exists $self->{teams}->{$name};
295 1         4 return $self->{teams}->{$name};
296             }
297 0           return values %{$self->{teams}};
  0            
298             }
299              
300             sub report_teams
301             {
302 0     0 0   my ($self, @cols) = @_;
303 0           print join("\t", @cols), "\n";
304 0           for ($self->teams) {
305 0           $_->print(@cols);
306             }
307             }
308              
309             =item pitchers
310              
311             Return all pitchers, i.e., NP (Number of Pitches) > 0.
312              
313             =item batters
314              
315             Return all batters, i.e., PA (Plate Appearances) > 0.
316              
317             =back
318              
319             =cut
320              
321              
322             =head1 AUTHOR
323              
324             Victor Hsieh, C<< >>
325              
326             =head1 BUGS
327              
328             Please report any bugs or feature requests to
329             C, or through the web interface at
330             L.
331             I will be notified, and then you'll automatically be notified of progress on
332             your bug as I make changes.
333              
334             =head1 SUPPORT
335              
336             You can find documentation for this module with the perldoc command.
337              
338             perldoc Baseball::Sabermetrics
339              
340             You can also look for information at:
341              
342             =over 4
343              
344             =item * AnnoCPAN: Annotated CPAN documentation
345              
346             L
347              
348             =item * CPAN Ratings
349              
350             L
351              
352             =item * RT: CPAN's request tracker
353              
354             L
355              
356             =item * Search CPAN
357              
358             L
359              
360             =back
361              
362             =head1 COPYRIGHT & LICENSE
363              
364             Copyright 2006 Victor Hsieh, all rights reserved.
365              
366             This program is free software; you can redistribute it and/or modify it
367             under the same terms as Perl itself.
368              
369             =cut
370              
371             1; # End of Baseball::Sabermetrics