sec-api.ioSEC API by D2V
Core APIs
Find & Track
EDGAR Filing Query APIFull-Text Search APIReal-Time Stream APIDaily Filing Ingestion Log
File Access
Download Filings & ExhibitsPDF GeneratorBulk Datasets
Extraction & Conversion
XBRL-to-JSON ConverterFinancial Statements + MoreFinancial StatementsFiling Section Extractor
Reference
EDGAR Entities DatabaseCIK / CUSIP / Ticker Mapping
AI Agents
Claude, ChatGPT & 50+ agents
MCP ServerFinancial Analysis Prompt LibraryEquity Research & Market Insights
Ownership & Corp. Events
Ownership & Insiders
Insider TradingForm 3/4/5Institutional HoldingsForm 13F5% Beneficial Owners13D / 13GInsider Sale NoticesForm 144
Event-Driven Disclosures
Auditor Changes8-K - 4.01Non-Reliance / Restatements8-K - 4.02Exec & Director Changes8-K - 5.02
Company Profile Data
Governance
Executive CompensationDirectors & Board Members
Capital Structure
Outstanding Shares & FloatSubsidiaries
Audit
Audit ReportsAudit Fees
Funds & Advisers
Registered Funds
Fund Portfolio Holdings and PerformanceForm N-PORTAnnual ReportsForm N-CENProxy Voting RecordsForm N-PX
Investment Advisers
Adviser RegistrationsForm ADV, Schedules, Brochures
Broker-Dealers
FOCUS ReportsForm X-17A-5
Offerings & Capital
Public Offerings
IPOs & SecondariesS-1 / F-1 / 424B4
Exempt Offerings
Private PlacementsForm DCrowdfundingForm CMini-IPOsReg A+
Enforcement & Regulatory
SEC Enforcement ActionsSEC Litigation ReleasesSEC Administrative ProceedingsAAER DatabaseSRO Filings & Rule MakingFINRA, Exchanges, Clearing Agencies
For developers
QuickstartAPI ReferencePython SDK ↗TypeScript SDK ↗Cookbook ↗
Browse all APIs →Browse all datasets →Full API docs →API reference →
Annual & Quarterly Reports
Annual reports10-KQuarterly reports10-QForeign annual20-FCanadian annual40-F
Material Events & Disclosures
Current reports8-KForeign current6-KReg A events1-UEarnings results8-K, Item 2.02Press releases + Reg FDEx 99Material contractsEx 10Insider trade policiesEx 19
Proxy & Shareholder Voting
Proxy statementsDEF 14APreliminaryPRE 14AAdditionalDEFA14ANon-management solicitingDFAN14AMerger proxy statementsDEFM14ADefinitive infoDEF 14CPreliminary infoPRE 14CAnnual report to SHARS
Corporate Insiders (§16)
Initial ownershipForm 3Insider TradingForm 4Annual deferredForm 5Restricted salesForm 144
Institutional Holdings
Portfolio holdings13F-HRCover pages13F-HR
Beneficial Ownership (5%+)
Active / activist13DPassive / institutional13G
Broker-Dealers & Market Infrastructure
BD annual reportsX-17A-5Transfer agent regTA-1Transfer agent rptTA-2Municipal advisorsMAAlt trading systemsATS-NRating agenciesNRSRO-CESwap dealersSBSE
SEC Administrative
Comment lettersCorresp / LetterEffectivenessEFFECTConfidential treatment ordersCT ORDER
Fund Registration & Holdings
Mutual funds & ETFsN-1AClosed-end & BDCsN-2Portfolio holdingsN-PORTMoney market fund portfolio reportsN-MFPSeparate accountN-3Variable annuityN-4Variable life insuranceN-6Fund mergersN-14
Business Development Cos
BDC electionN-54ABDC registrationN-2BDC auto shelfN-2ASR
Fund Reports
Annual censusN-CENShareholder reportsN-CSRSemi-annual shareholder reportsN-CSRSMoney market eventsN-CRProxy votingN-PXVariable productsN-VP
Fund Prospectuses & Supplements
Fund prospectus497Summary prospectus497KVariable initial summary497VPIVariable updating summary497VPUPost-effective (delayed)485APOSPost-effective (immediate)485BPOS
Fund Operations
Securities sold24F-2NTRepurchase offersN-23CDeregistrationN-8FExemptive apps40-APP
Public Offerings — Domestic
IPOs & registrationS-1Shelf registrationsS-3Business combinationsS-4Employee plansS-8REITsS-11
Public Offerings — Foreign
Foreign registrationF-1Foreign shelfF-3Foreign combinationsF-4ADRsF-6Rights offeringF-7Business combinationF-8MJDS registrationF-10MJDS exchange/tenderF-80
Prospectuses (424 series)
Structured products424B2Prospectus supplements424B3Final IPO424B4Shelf supplements424B5Free writingFWPM&A communications425
Exempt Offerings
Reg D privateForm DReg A offerings1-AReg A annual1-KReg A semi-annual1-SACrowdfundingForm CFunding portalsCFPORTAL
Asset-Backed Securities
Standard ABS registrationSF-1Shelf ABS registrationSF-3Distribution reports10-DAsset-level dataABS-EEReps & warrantiesABS-15G
Tender Offers & Going Private
Pre-commencement commsSC TO-CThird-partySC TO-TIssuer self-tenderSC TO-ITarget responseSC 14D9Going privateSC 13E3Cross-border transactionsCB
Confidential Submissions
Draft registrationsDRSDraft Reg ADOS
Browse all 400+ datasets →Datasets catalog →
PricingPlaygroundDocs
Setup
Python SDK ↗TypeScript SDK ↗Lucene Query Syntax
Fetch & download
Download SEC Filings From EDGARDownload Filings As PDFsDownload XML From EDGARDownload 10-Ks (Russell 3000)Financial Statements As ExcelExport Filing Metadata To CSVList Fiscal Period End Dates
Reference
Map Ticker → CIK & CUSIPMap CIK → Ticker & CUSIPMap CUSIP → Ticker & CIKList Companies By ExchangeList Companies By Sector & Industry
AI / LLM
Summarize SEC Filings With GPT ↗Analyze SEC Comment Letters
Section & text extraction
Extract 10-K SectionsExtract 10-Q SectionsExtract 8-K SectionsExtract 10-K (Multi-Year)Clean Extracted SectionsExtract 10-K Textual DataExtract Google Revenue (10-K)
Financial data
Extract Financial StatementsXBRL-to-JSON ConverterDownload XBRL XSD Files
Exhibits & attachments
Download 8-K Exhibit 99Download 8-K Press ReleasesDownload Material Contracts (Ex-10)
Governance & compensation
Exec Comp By TickerExec Comp Multi-Company CompareExport Exec Comp To ExcelDirectors & Board MembersAudit FeesSubsidiariesOutstanding Shares & Float
Market events & disclosures
8-K Material Event ActivityStock Reactions To 8-K 4.028-K Item 4.01Accountant Changes8-K Item 5.02Exec Changes
Ownership & fund analysis
Insider Trading AnalysisInsider Trading Analysis - Extended ↗13F Alpha, Volatility & Survival13F Portfolio Holdings & Cover PagesN-PORT Swap CounterpartiesN-PORT ETF ConstituentsForm 13D / 13G API ExamplesForm 144 Insider Sales
Investment advisers (Form ADV)
Recently Updated Form ADVsFind By Advisory ActivityFind By Business ActivityFind By Compensation ModelSort By Assets Under ManagementSort By Number Of AccountsFind Individual Advisers By CRD
Offerings analysis
Top 20 Largest Offerings / YrMost-Used Law Firms (S-1)424B4 Prospectuses By YearForm C CrowdfundingForm D Private PlacementsReg A+ Offerings
Quant & research
Fed Funds Rate ProjectionsFama-French Factor ModelStatistical SignificanceHalloween Effect ExplainedEDGAR Filing Trends 1994-2022EDGAR Filer Analysis
Enforcement research
SEC Litigation ReleasesSEC Administrative ProceedingsAAERs
More
Real-Time StreamFull-Text SearchForm N-CENForm N-PX Proxy VotingAll Per-API Examples →
Regulatory explainers
EDGAR Filings & ExhibitsIntro to SEC LawFiling Entities & Disclosure RequirementsForm Types Supporting XBRL8-K Item 4.01 Content Requirements
Browse All Tutorials →Cookbook on GitHub ↗
API Documentation
Introduction

AI Assistants

MCP Server

Filing Search and Retrieval

Filing Search
Full-Text Search
EDGAR Ingestion Log
Real-Time Filing Stream
Filing Download
PDF Generator
XBRL to JSON
Section Extractor
Bulk Datasets

Investment Advisers

Firm and Individual AdvisorsADV

Ownership and Holdings

Insider Transactions and Holdings3 / 4 / 5
Restricted Securities Sales144
Institutional Portfolio Holdings13F
Beneficial Ownership Above 5%13D / 13G
Insider & 5% Holder OwnershipDEF 14A
Fund Portfolio Holdings and PerformanceN-PORT

Investment Companies

Fund Annual Census ReportsN-CEN
Proxy Voting and Voting RecordsN-PX
Overview
Example: Python
API Reference
Fund Governance & Fees
Fund Reorganizations

Offerings and Capital Raises

Registrations and ProspectusesS-1 / 424B4
Crowdfunding OfferingsForm C
Exempt Private OfferingsForm D
Reg A Offerings1-A / 1-K / 1-Z

Material Events

Entry into Material Agreements8-K 1.01
Agreement Terminations8-K 1.02
Bankruptcy and Receivership8-K 1.03
Cybersecurity Incidents8-K 1.05
Auditor Changes8-K 4.01
Financial Restatements8-K 4.02
Director and Officer Changes8-K 5.02
Shareholder Voting Results8-K 5.07

Executive and Director Pay

Executive Compensation
Director Compensation
Pay Versus Performance
Executive Compensation Governance
Severance & Change in Control

Board and Corporate Governance

Directors and Board Members
Board Oversight
Shareholder Rights & Defenses
Related Party Transactions
ESG & Risk Oversight
Compliance Signals

Shareholder Meetings and Votes

Ballot Items & Vote Standards
Shareholder Meetings Calendar
Shareholder Proposals and Proxy Contests
M&A & Strategic Transactions

Audit and Company Data

Audit Reports
Audit Fees
Subsidiaries
Shares Outstanding and Float
Equity Plans & Dilution

Broker-Dealers

FOCUS ReportsX-17A-5

SEC Enforcement and Rulemaking

Enforcement Actions
Litigation Releases
Administrative Proceedings
AAERs
SRO Filings

Identifiers and Entities

CIK, CUSIP and Ticker Mapping
EDGAR Entities

Financial Statements
  1. Home
  2. Documentation
  3. Proxy Voting and Voting Records
  4. Example: Python
View as MarkdownOpen this page as plain Markdown
Open in ChatGPTAsk questions about this pageOpen in ClaudeAsk questions about this pageOpen in CursorAsk questions about this page

Analysis of Proxy Voting Disclosures

Open In Colab   View on GitHub

On this page:
  • Quick Start
    • Download Dataset
      • Downloading Metadata
      • Analyzing Meta Data
        • Proxy Voting Disclosure Statistics
          • Form Type
            • Reporting Cadence
              • Registrant Type
                • Filing includes Proxy Voting Records
                • Report Type
                  • Confidentiality Requests
                    • Joint Proxy Voting Disclosures
                      • Proxy Voting Records
                        • Downloading Proxy Voting Records
                          • Vote Categories
                            • Shares on Loan
                              • Distribution of votes among filers
                                • Votes for specific company - example Tesla
                                  • Voting behavior of large funds

                                This notebook illustrates how to perform exploratory data analysis on disclosures that inform investors about voting behavior on shareholder proposals by registered investment companies, such as mutual funds and ETFs, and certain institutional investment managers, such as hedge funds and pension funds. These voting records are disclosed in Form N-PX filings with the SEC. Since 2024, these disclosures have been presented in XML format by companies. Utilizing our Form N-PX Proxy Voting Records API, we convert the data to a standardized JSON format, making it available for detailed analysis.

                                Our analysis will focus on several key areas:

                                • Number of Form N-PX disclosures made for the year 2024, per quarter, month and at what time of the day (pre-market, regular market, after-market)
                                • Distribution of disclosures across structured data fields, such as the proportion of disclosures by report type, joint disclosure of proxy votes, and requests for confidentiality
                                • Distribution of proxy votes across vote categories
                                • Proportion of shares loaned for proxy votes
                                • Distribution of proxy votes across funds
                                • An exploration of the voting behavior of large funds during the 2024 Tesla shareholder meeting
                                import os
                                import json
                                import random

                                # from multiprocessing import Pool # use in .py files only
                                from concurrent.futures import ThreadPoolExecutor
                                import pandas as pd
                                import numpy as np
                                import matplotlib.pyplot as plt
                                import matplotlib.style as style
                                import seaborn as sns

                                style.use("default")

                                params = {
                                    "axes.labelsize": 8,
                                    "font.size": 8,
                                    "legend.fontsize": 8,
                                    "xtick.labelsize": 8,
                                    "ytick.labelsize": 8,
                                    "font.family": "sans-serif",
                                    "axes.spines.top": False,
                                    "axes.spines.right": False,
                                    "grid.color": "grey",
                                    "axes.grid": True,
                                    "axes.grid.axis": "y",
                                    "grid.alpha": 0.5,
                                    "grid.linestyle": ":",
                                }

                                plt.rcParams.update(params)

                                Quick Start

                                If data for a specific fund should be downloaded right away, the next example can be adjusted to your liking. For more detail, see Form N-PX Proxy Voting Records API and sec-api-python package readme.

                                !pip install sec-api
                                from sec_api import FormNPXApi

                                formNpxApi = FormNPXApi(api_key="YOUR_API_KEY")

                                search_params = {
                                    "query": "cik:884546",
                                    "from": "0",
                                    "size": "1",
                                    "sort": [{"filedAt": {"order": "desc"}}],
                                }

                                # get N-PX filing metadata: registrant type, investment company type,
                                # series and class IDs, report type, period of report, and more
                                response = formNpxApi.get_metadata(search_params)
                                npx_filing_metadata = response["data"]

                                print(json.dumps(npx_filing_metadata, indent=2))
                                [
                                  {
                                    "id": "f1b4fa011e08f98470f8ec2f49aa3c26",
                                    "accessionNo": "0001021408-24-003901",
                                    "formType": "N-PX",
                                    "filedAt": "2024-08-23T18:54:16-04:00",
                                    "periodOfReport": "2024-06-30",
                                    "cik": "884546",
                                    "ticker": "",
                                    "companyName": "CHARLES SCHWAB INVESTMENT MANAGEMENT INC",
                                    "proxyVotingRecordsAttached": true,
                                    "headerData": {
                                      "submissionType": "N-PX",
                                      "filerInfo": {
                                        "registrantType": "IM",
                                        "filer": {
                                          "issuerCredentials": {
                                            "cik": "0000884546"
                                          }
                                        },
                                        "flags": {
                                          "overrideInternetFlag": false,
                                          "confirmingCopyFlag": false
                                        },
                                        "periodOfReport": "06/30/2024"
                                      }
                                    },
                                    "formData": {
                                      "coverPage": {
                                        "yearOrQuarter": "YEAR",
                                        "reportCalendarYear": "2024",
                                        "reportingPerson": {
                                          "name": "Charles Schwab Investment Management Inc",
                                          "phoneNumber": "4156677000",
                                          "address": {
                                            "street1": "211 Main Street",
                                            "city": "San Fransisco",
                                            "stateOrCountry": "CA",
                                            "zipCode": "94105"
                                          }
                                        },
                                        "agentForService": {},
                                        "reportInfo": {
                                          "reportType": "INSTITUTIONAL MANAGER VOTING REPORT",
                                          "confidentialTreatment": false
                                        },
                                        "fileNumber": "028-03128",
                                        "explanatoryInformation": {
                                          "explanatoryChoice": false
                                        }
                                      },
                                      "summaryPage": {
                                        "otherIncludedManagersCount": 0
                                      },
                                      "signaturePage": {
                                        "reportingPerson": "Charles Schwab Investment Management Inc",
                                        "txSignature": "Omar Aguilar",
                                        "txPrintedSignature": "Omar Aguilar",
                                        "txTitle": "Chief Executive Officer",
                                        "txAsOfDate": "08/19/2024"
                                      }
                                    }
                                  }
                                ]
                                # get proxy voting records: issuer name, CUSIP, meeting date, vote description,
                                # vote categories, shares voted, how voted, management recommendation, and more
                                accessionNo = npx_filing_metadata[0]["accessionNo"]
                                response = formNpxApi.get_voting_records(accessionNo)
                                voting_records = response["proxyVotingRecords"]

                                print(json.dumps(voting_records[0], indent=2))
                                {
                                  "issuerName": "1-800-FLOWERS.COM, Inc.",
                                  "cusip": "68243Q106",
                                  "meetingDate": "12/14/2023",
                                  "voteDescription": "To Approve, On An Advisory Basis, The Company's Executive Compensation For Its Named Executive Officers.",
                                  "voteCategories": {
                                    "voteCategory": [
                                      {
                                        "categoryType": "SECTION 14A SAY-ON-PAY VOTES"
                                      }
                                    ]
                                  },
                                  "voteSource": "ISSUER",
                                  "sharesVoted": 577284,
                                  "sharesOnLoan": 0,
                                  "vote": {
                                    "voteRecord": [
                                      {
                                        "howVoted": "FOR",
                                        "sharesVoted": 577284,
                                        "managementRecommendation": "FOR"
                                      }
                                    ]
                                  }
                                }

                                Download Dataset

                                To load and prepare the dataset of over 10,000 proxy voting record disclosures from Form N-PX in 2024, we utilize the Form N-PX Proxy Voting Records API. The following code handles data loading and preparation by executing multiple download processes in parallel, significantly reducing downloading time. The API has two parts: the Search API for the metadata of the filings and the Voting Records API to retrieve the full voting records.

                                Once downloaded, all metadata objects are saved in JSONL format to ./form-npx-meta-data.jsonl, which will serve as the dataset for the first part of the analysis. The proxy voting records are downloaded further down in the notebook and saved as individual files in the directory ./form-npx-proxy-voting-records as gzipped json files.

                                Downloading the metadata may take several minutes.

                                Downloading Metadata

                                import sys
                                import time

                                YEARS = range(2025, 2023, -1) # from 2024 to 2004
                                TEMP_FILE_TEMPLATE = "./temp_file_npx_{}.jsonl"
                                TARGET_FILE = "./form-npx-meta-data.jsonl.jsonl"


                                def process_year(year):
                                    backoff_time = random.randint(10, 800) / 1000
                                    print(f"Starting year {year} with backoff time {backoff_time:,}s")
                                    sys.stdout.flush()
                                    time.sleep(backoff_time)

                                    tmp_filename = TEMP_FILE_TEMPLATE.format(year)
                                    tmp_file = open(tmp_filename, "a")

                                    for month in range(12, 0, -1):
                                        search_from = 0
                                        month_counter = 0

                                        while True:
                                            query = f"filedAt:[{year}-{month:02d}-01 TO {year}-{month:02d}-31]"
                                            searchRequest = {
                                                "query": query,
                                                "from": search_from,
                                                "size": "50",
                                                "sort": [{"filedAt": {"order": "desc"}}],
                                            }

                                            response = None
                                            try:
                                                response = form_NPX_Api.get_metadata(searchRequest)
                                            except Exception as e:
                                                print(f"{year}-{month:02d} error: {e}")
                                                sys.stdout.flush()
                                                continue

                                            if response == None or len(response["data"]) == 0:
                                                break

                                            search_from += 50
                                            month_counter += len(response["data"])
                                            jsonl_data = "\n".join([json.dumps(entry) for entry in response["data"]])
                                            tmp_file.write(jsonl_data + "\n")

                                        print(f"Finished loading {month_counter} Form N-PX for {year}-{month:02d}")
                                        sys.stdout.flush()

                                    tmp_file.close()

                                    return year


                                if not os.path.exists(TARGET_FILE):
                                    with ThreadPoolExecutor(max_workers=4) as pool:
                                        processed_years = list(pool.map(process_year, YEARS))
                                    print("Finished processing all years.", processed_years)

                                    # merge the temporary files into one final file
                                    with open(TARGET_FILE, "a") as outfile:
                                        for year in YEARS:
                                            temp_file = TEMP_FILE_TEMPLATE.format(year)
                                            if os.path.exists(temp_file):
                                                with open(temp_file, "r") as infile:
                                                    outfile.write(infile.read())
                                else:
                                    print("File already exists. Skipping download.")
                                File already exists. Skipping download.

                                Analyzing Meta Data

                                structured_data = pd.read_json(TARGET_FILE, lines=True)


                                # Convert stringified JSON in 'formData' and 'headerData' into dictionaries
                                def safe_json_load(value):
                                """Convert stringified JSON to a dictionary safely."""
                                    if isinstance(value, str):
                                        try:
                                            return json.loads(value) # Convert string to dict
                                        except json.JSONDecodeError:
                                            return {} # Return empty dict if parsing fails
                                    return value # Return as-is if it's already a dictionary


                                # Manually normalize formData and headerData
                                formData_flat = pd.json_normalize(structured_data["formData"].apply(safe_json_load))
                                headerData_flat = pd.json_normalize(structured_data["headerData"].apply(safe_json_load))

                                # Add prefix manually to avoid conflicts
                                formData_flat = formData_flat.add_prefix("formData.")
                                headerData_flat = headerData_flat.add_prefix("headerData.")

                                # Merge back into the main dataframe
                                structured_data = pd.concat(
                                    [
                                        structured_data.drop(columns=["formData", "headerData"]),
                                        formData_flat,
                                        headerData_flat,
                                    ],
                                    axis=1,
                                )

                                structured_data["filedAt"] = pd.to_datetime(structured_data["filedAt"], utc=True)
                                structured_data["filedAt"] = structured_data["filedAt"].dt.tz_convert("US/Eastern")
                                structured_data = structured_data.sort_values("filedAt", ascending=True).reset_index(
                                    drop=True
                                )
                                structured_data.drop_duplicates("accessionNo", keep="first", inplace=True)
                                structured_data["year"] = structured_data["filedAt"].dt.year
                                structured_data["month"] = structured_data["filedAt"].dt.month
                                structured_data["dayOfWeek"] = structured_data["filedAt"].dt.day_name()
                                # filedAtClass: preMarket (4:00AM-9:30AM), regularMarket (9:30AM-4:00PM), afterMarket (4:00PM-8:00PM)
                                structured_data["filedAtClass"] = structured_data["filedAt"].apply(
                                    lambda x: (
                                        "preMarket"
                                        if x.hour < 9 or (x.hour == 9 and x.minute < 30)
                                        else (
                                            "regularMarket"
                                            if x.hour < 16
                                            else "afterMarket" if x.hour < 20 else "other"
                                        )
                                    )
                                )

                                structured_data.head()
                                Out[6]:
                                idaccessionNoformTypefiledAtperiodOfReportciktickercompanyNameproxyVotingRecordsAttachedformData.coverPage.yearOrQuarter...headerData.filerInfo.flags.confirmingCopyFlagheaderData.filerInfo.periodOfReportheaderData.filerInfo.investmentCompanyTypeheaderData.seriesClass.reportSeriesClass.rptSeriesClassInfoheaderData.seriesClass.reportSeriesClass.rptIncludeAllSeriesFlagheaderData.filerInfo.filer.fileNumberyearmonthdayOfWeekfiledAtClass
                                02c5fe30e8becbdf38b14ac931146d8690001536924-24-000004N-PX2024-07-01 07:20:38-04:002024-06-301536924LBMC INVESTMENT ADVISORS, LLCFalseYEAR...False06/30/2024NaNNaNNaNNaN20247MondaypreMarket
                                1656253ad506ece064f6531602aef8ba80001062993-24-013157N-PX2024-07-01 09:40:16-04:002024-06-301891795Bromfield Sneider Wealth AdvisorsFalseYEAR...False06/30/2024NaNNaNNaNNaN20247MondayregularMarket
                                2c82fbdb4371918ef4f35c01e0a97ba090001172661-24-002608N-PX2024-07-01 09:41:00-04:002024-06-301906613Solidarity Wealth, LLCFalseYEAR...False06/30/2024NaNNaNNaNNaN20247MondayregularMarket
                                3ad09ed64cdade450f45b80753fc7ce4d0001843283-24-000003N-PX2024-07-01 11:09:37-04:002024-06-301843283Touchstone Capital, Inc.FalseYEAR...False06/30/2024NaNNaNNaNNaN20247MondayregularMarket
                                4c132f6c6a4d214966d80b889e4768c010002020726-24-000003N-PX2024-07-01 11:26:16-04:002024-07-012020726McCarthy & CoxFalseYEAR...False07/01/2024NaNNaNNaNNaN20247MondayregularMarket

                                5 rows × 66 columns

                                unique_years = structured_data["year"].nunique()
                                unique_companies = structured_data["cik"].nunique()
                                unique_filings = structured_data["accessionNo"].nunique()
                                min_year = structured_data["year"].min()
                                max_year = structured_data["year"].max()
                                print("Loaded dataframe with main documents of Form N-PX Proxy filings")
                                print(f"Number of filings: {unique_filings:,}")
                                print(f"Number of records: {len(structured_data):,}")
                                print(f"Number of years: {unique_years:,} ({min_year}-{max_year})")
                                print(f"Number of unique companies: {unique_companies:,}")

                                # structured_data.head()
                                Loaded dataframe with main documents of Form N-PX Proxy filings
                                Number of filings: 10,433
                                Number of records: 10,433
                                Number of years: 2 (2024-2025)
                                Number of unique companies: 9,311

                                Proxy Voting Disclosure Statistics

                                npx_counts = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["year"])
                                    .size()
                                    .to_frame(name="count")
                                )

                                print(f"N-PX counts from 2024 to 2025.")
                                npx_counts.T
                                N-PX counts from 2024 to 2025.
                                Out[8]:
                                year20242025
                                count1037558
                                counts_month_yr_piv = (
                                    structured_data.groupby(["year", "month"]).size().unstack().fillna(0)
                                ).astype(int)

                                # Ensure all months (1-12) are included in the index
                                counts_month_yr_piv = counts_month_yr_piv.reindex(columns=range(1, 13), fill_value=0)

                                # Convert to integer for better readability
                                counts_month_yr_piv = counts_month_yr_piv.astype(int)

                                print(f"Form N-PX counts by quarter from 2024 to 2025.")
                                counts_month_yr_piv.T
                                Form N-PX counts by quarter from 2024 to 2025.
                                Out[9]:
                                year20242025
                                month
                                1026
                                2032
                                300
                                400
                                500
                                600
                                77880
                                885870
                                97620
                                101360
                                11670
                                12350
                                structured_data["qtr"] = structured_data["month"].apply(lambda x: (x - 1) // 3 + 1)

                                counts_qtr_yr_piv = (
                                    structured_data.groupby(["year", "qtr"]).size().unstack().fillna(0)
                                ).astype(int)

                                counts_qtr_yr_piv = counts_qtr_yr_piv.reindex(columns=range(1, 5), fill_value=0)

                                print(f"Form N-PX counts by month from 2024 to 2025.")
                                counts_qtr_yr_piv.T
                                Form N-PX counts by month from 2024 to 2025.
                                Out[10]:
                                year20242025
                                qtr
                                1058
                                200
                                3101370
                                42380

                                Because there are so few reports in 2025 at the time of writing, let us restrict the following analysis to filings with report period in 2024.

                                print(f"Length of structured_data: {len(structured_data)}")
                                structured_data = structured_data[
                                    structured_data["periodOfReport"].apply(lambda x: x.split("-")[0] == "2024")
                                ]
                                print("length of structured_data after filtering for 2024: ", len(structured_data))
                                Length of structured_data: 10433
                                length of structured_data after filtering for 2024: 10412
                                counts_filedAtClass = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["filedAtClass"])
                                    .size()
                                    .sort_values(ascending=False)
                                    .to_frame(name="Count")
                                ).rename_axis("Publication Time")
                                counts_filedAtClass["Pct"] = (
                                    counts_filedAtClass["Count"].astype(int)
                                    / counts_filedAtClass["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                counts_filedAtClass["Count"] = counts_filedAtClass["Count"].map(lambda x: f"{x:,}")
                                counts_filedAtClass.index = (
                                    counts_filedAtClass.index.str.replace("preMarket", "Pre-Market (4:00 - 9:30 AM)")
                                    .str.replace("marketHours", "Market Hours (9:30 AM - 4:00 PM)")
                                    .str.replace("afterMarket", "After Market (4:00 - 8:00 PM)")
                                )
                                counts_filedAtClass = counts_filedAtClass.reindex(counts_filedAtClass.index[::-1])

                                print(
                                    f"N-PX counts by pre-market, regular market hours,\nand after-market publication time (2024)."
                                )
                                counts_filedAtClass
                                N-PX counts by pre-market, regular market hours,
                                and after-market publication time (2024).
                                Out[12]:
                                CountPct
                                Publication Time
                                other3183%
                                Pre-Market (4:00 - 9:30 AM)1,25912%
                                After Market (4:00 - 8:00 PM)2,87328%
                                regularMarket5,96257%
                                counts_dayOfWeek = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["dayOfWeek"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Day of the Week")
                                counts_dayOfWeek["Pct"] = (
                                    counts_dayOfWeek["Count"].astype(int) / counts_dayOfWeek["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                counts_dayOfWeek["Count"] = counts_dayOfWeek["Count"].map(lambda x: f"{x:,}")

                                print(f"Form N-PX disclosures by day of the week (2024).")
                                counts_dayOfWeek.loc[["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]]
                                Form N-PX disclosures by day of the week (2024).
                                Out[13]:
                                CountPct
                                Day of the Week
                                Monday1,16911%
                                Tuesday2,26922%
                                Wednesday1,81817%
                                Thursday2,28722%
                                Friday2,86928%

                                Form Type

                                count_formType = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["headerData.submissionType"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Submission Type")
                                count_formType["Pct"] = (
                                    count_formType["Count"].astype(int) / count_formType["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_formType["Count"] = count_formType["Count"].map(lambda x: f"{x:,}")

                                print(f"Form N-PX Disclosures by Submission Type (2024)")
                                count_formType
                                Form N-PX Disclosures by Submission Type (2024)
                                Out[14]:
                                CountPct
                                Submission Type
                                N-PX10,24398%
                                N-PX/A1692%
                                count_amendmentType = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["formData.coverPage.amendmentInfo.amendmentType"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Amendment Type")
                                count_amendmentType["Pct"] = (
                                    count_amendmentType["Count"].astype(int)
                                    / count_amendmentType["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_amendmentType["Count"] = count_amendmentType["Count"].map(lambda x: f"{x:,}")
                                count_amendmentType["Pct tot"] = (
                                    count_amendmentType["Count"].astype(int) / len(structured_data) * 100
                                ).map("{:.2f}%".format)
                                count_amendmentType.sort_values("Count", ascending=True, inplace=True)

                                print(f"Form N-PX/A Amendment Disclosures by Amendment Type (2024)")
                                count_amendmentType
                                Form N-PX/A Amendment Disclosures by Amendment Type (2024)
                                Out[15]:
                                CountPctPct tot
                                Amendment Type
                                RESTATEMENT14484%1.38%
                                NEW PROXY2816%0.27%

                                Reporting Cadence

                                count_quarterOrYear = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(structured_data["formData.coverPage.yearOrQuarter"].str.strip())
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Report Cadence")
                                count_quarterOrYear["Pct"] = (
                                    count_quarterOrYear["Count"].astype(int)
                                    / count_quarterOrYear["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_quarterOrYear.sort_values("Count", ascending=False, inplace=True)
                                count_quarterOrYear["Count"] = count_quarterOrYear["Count"].map(lambda x: f"{x:,}")

                                print(f"Disclosures by Quarterly or Yearly Reporting Cadence (2024)")
                                count_quarterOrYear
                                Disclosures by Quarterly or Yearly Reporting Cadence (2024)
                                Out[16]:
                                CountPct
                                Report Cadence
                                YEAR10,35699%
                                QUARTER561%

                                Registrant Type

                                Type of the entity fling the proxy voting disclosure form.

                                count_registrantType = (
                                    structured_data.drop_duplicates(subset=["cik"])
                                    .groupby(structured_data["headerData.filerInfo.registrantType"].str.strip())
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Registrant Type")
                                count_registrantType["Pct"] = (
                                    count_registrantType["Count"].astype(int)
                                    / count_registrantType["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_registrantType["Count"] = count_registrantType["Count"].map(lambda x: f"{x:,}")

                                print(
                                    f"Number of Form N-PX Filers by Registrant Type (2024)\n"
                                    f"IM = Institutional Manager\n"
                                    f"RMIC = Registered Management Investment Company"
                                )

                                count_registrantType
                                Number of Form N-PX Filers by Registrant Type (2024)
                                IM = Institutional Manager
                                RMIC = Registered Management Investment Company
                                Out[17]:
                                CountPct
                                Registrant Type
                                IM7,19577%
                                RMIC2,10523%

                                Filing includes Proxy Voting Records

                                count_includesProxyVotingRecords = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["proxyVotingRecordsAttached"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Includes Proxy Voting Records")
                                count_includesProxyVotingRecords.sort_values("Count", ascending=False, inplace=True)
                                count_includesProxyVotingRecords["Pct"] = (
                                    count_includesProxyVotingRecords["Count"].astype(int)
                                    / count_includesProxyVotingRecords["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_includesProxyVotingRecords["Count"] = count_includesProxyVotingRecords[
                                    "Count"
                                ].map(lambda x: f"{x:,}")

                                print(f"Form N-PX Filings by Voting Data Availability (2024)")
                                count_includesProxyVotingRecords
                                Form N-PX Filings by Voting Data Availability (2024)
                                Out[18]:
                                CountPct
                                Includes Proxy Voting Records
                                True5,43152%
                                False4,98148%

                                Report Type

                                count_reportType = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(["formData.coverPage.reportInfo.reportType"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Registrant Type")
                                count_reportType["Pct"] = (
                                    count_reportType["Count"].astype(int) / count_reportType["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_reportType.sort_values(by="Count", ascending=False, inplace=True)
                                count_reportType["Count"] = count_reportType["Count"].map(lambda x: f"{x:,}")

                                print(f"Number of Form N-PX Filings by Report Type (2024)\n")

                                count_reportType
                                Number of Form N-PX Filings by Report Type (2024)

                                Out[19]:
                                CountPct
                                Registrant Type
                                INSTITUTIONAL MANAGER NOTICE REPORT3,89837%
                                INSTITUTIONAL MANAGER VOTING REPORT3,31132%
                                FUND VOTING REPORT1,96819%
                                FUND NOTICE REPORT1,07910%
                                INSTITUTIONAL MANAGER COMBINATION REPORT1561%
                                • FUND VOTING REPORT: full proxy voting record filed by an investment company
                                • FUND NOTICE REPORT: indicates that no securities were held for voting
                                • INSTITUTIONAL MANAGER VOTING REPORT: full proxy voting report filed by an institutional manager
                                • INSTITUTIONAL MANAGER NOTICE REPORT: indicates that no votes were cast
                                • INSTITUTIONAL MANAGER COMBINATION REPORT: partial voting report where some votes are reported elsewhere
                                count_noticeReason = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(
                                        structured_data["formData.coverPage.reportInfo.noticeExplanation"].str.strip()
                                    ) # Strip inside groupby
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Notice Explanation")

                                count_noticeReason["Pct"] = (
                                    count_noticeReason["Count"].astype(int)
                                    / count_noticeReason["Count"].astype(int).sum()
                                ).map("{:.0%}".format)
                                count_noticeReason["Pct tot"] = (
                                    count_noticeReason["Count"].astype(int) / len(structured_data) * 100
                                ).map("{:.2f}%".format)
                                count_noticeReason.sort_values(by="Count", ascending=False, inplace=True)
                                count_noticeReason["Count"] = count_noticeReason["Count"].map(lambda x: f"{x:,}")

                                print(f"Number of Form N-PX Filings by reason for why\n" "no votes were cast (2024)")

                                count_noticeReason
                                Number of Form N-PX Filings by reason for why
                                no votes were cast (2024)
                                Out[20]:
                                CountPctPct tot
                                Notice Explanation
                                REPORTING PERSON HAS POLICY TO NOT VOTE2,27158%21.81%
                                REPORTING PERSON DID NOT EXERCISE VOTING1,02426%9.83%
                                ALL VOTES BY OTHER PERSONS60315%5.79%

                                Confidentiality Requests

                                Filers of Form N-PX can request that part of the proxy voting records are omitted due to confidentiality reasons.

                                count_confidentiality = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(structured_data["formData.coverPage.reportInfo.confidentialTreatment"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Confidentiality Requested")
                                count_confidentiality["Pct"] = (
                                    count_confidentiality["Count"].astype(int)
                                    / count_confidentiality["Count"].astype(int).sum()
                                ).map("{:.2%}".format)
                                count_confidentiality["Count"] = count_confidentiality["Count"].map(lambda x: f"{x:,}")

                                print(f"Number of Form N-PX Filings with a Confidentiality Request (2024)")

                                count_confidentiality
                                Number of Form N-PX Filings with a Confidentiality Request (2024)
                                Out[21]:
                                CountPct
                                Confidentiality Requested
                                False7,72399.42%
                                True450.58%

                                Joint Proxy Voting Disclosures

                                count_otherManagers = (
                                    structured_data.drop_duplicates(subset=["accessionNo"])
                                    .groupby(
                                        structured_data["formData.summaryPage.otherIncludedManagersCount"].fillna(0)
                                    )
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Other Manager Count")
                                count_otherManagers.index = count_otherManagers.index.astype(int)

                                count_otherManagers["Pct"] = (
                                    count_otherManagers["Count"].astype(int)
                                    / count_otherManagers["Count"].astype(int).sum()
                                ).map("{:.2%}".format)
                                count_otherManagers.sort_values(by="Count", ascending=False, inplace=True)
                                count_otherManagers["Count"] = count_otherManagers["Count"].map(lambda x: f"{x:,}")

                                print(
                                    f"Number of Form N-PX Filings (2024)\n"
                                    "by Number Of Other Managers For Joint Reporting"
                                )

                                count_otherManagers.head(10)
                                Number of Form N-PX Filings (2024)
                                by Number Of Other Managers For Joint Reporting
                                Out[22]:
                                CountPct
                                Other Manager Count
                                09,51391.37%
                                16906.63%
                                2970.93%
                                3420.40%
                                4160.15%
                                5160.15%
                                680.08%
                                970.07%
                                760.06%
                                850.05%
                                structured_data["formData.summaryPage.otherIncludedManagersCount"].fillna(0).plot(
                                    kind="hist",
                                    bins=range(0, 26),
                                    color="steelblue",
                                    edgecolor="black",
                                    linewidth=0.5,
                                    figsize=(3.5, 2),
                                )
                                plt.grid(axis="x")
                                plt.yscale("log")
                                plt.xlabel("Number of Other Managers for Joint Reporting")
                                plt.ylabel("Number of N-PX Filings")
                                Out[23]:
                                Text(0, 0.5, 'Number of N-PX Filings')

                                Proxy Voting Records

                                Downloading Proxy Voting Records

                                Around half of the Form N-PX filings include proxy voting records, about 5000 for the 2024 report period. The following code shows how to download the records using the Form N-PX Proxy Voting Records API in parallel and save them saved as individual files in the directory ./form-npx-proxy-voting-records as gzipped json files. The files are stored in individual files to avoid memory issues when parsing the data into a pandas data frame.

                                Downloading the entire dataset may take up to 20 minutes.

                                accessionNo_with_voting_records = structured_data[
                                    structured_data["proxyVotingRecordsAttached"] == True
                                ][["accessionNo"]]
                                accessionNo_with_voting_records = [a[0] for a in accessionNo_with_voting_records.values]
                                accessionNo_with_voting_records[:5]
                                Out[24]:
                                ['0001213900-24-057688',
                                 '0001162044-24-000643',
                                 '0001172661-24-002634',
                                 '0001104659-24-077743',
                                 '0001766392-24-000004']
                                import json

                                YEARS = range(2025, 2023, -1) # from 2025 to 2024
                                DATA_DIR = "./form-npx-proxy-voting-records"

                                os.makedirs(DATA_DIR, exist_ok=True)


                                def download_proxy_voting_records(accessionNo):
                                    try:
                                        filename = f"{DATA_DIR}/{accessionNo}_proxy_voting_record.json"
                                        if os.path.exists(filename):
                                            return accessionNo
                                        response = formNpxApi.get_voting_records(accessionNo)
                                        with open(f"{DATA_DIR}/{accessionNo}_proxy_voting_record.json", "w") as outfile:
                                            json.dump(response, outfile)

                                        return accessionNo

                                    except Exception as e:
                                        print(f"Error downloading {accessionNo}: {e}")
                                        return None


                                num_downloaded = len(os.listdir(DATA_DIR))

                                if num_downloaded < len(accessionNo_with_voting_records):
                                    print("Downloading proxy voting records...")
                                    with ThreadPoolExecutor(max_workers=4) as pool:
                                        downloaded_files = list(
                                            pool.map(download_proxy_voting_records, accessionNo_with_voting_records)
                                        )

                                    num_success_download = len([f for f in downloaded_files if f])

                                    print(
                                        f"Finished downloading {num_success_download}/{len(accessionNo_with_voting_records)} proxy voting records."
                                    )
                                else:
                                    print("Files already exists. Skipping download.")
                                Files already exists. Skipping download.

                                To load the proxy voting records, we process them file by file. This process might take several minutes depending on the system.

                                import pandas as pd
                                import json
                                import gzip


                                def load_json_files(directory, ending=None):
                                """
                                Load all JSON files from a given directory into a single Pandas DataFrame.

                                :param directory: Path to the directory containing JSON files
                                :param ending: Optional file ending to filter files
                                :return: A concatenated Pandas DataFrame
                                """
                                    dataframes = []

                                    cnt = 1
                                    for root, dirs, files in os.walk(directory):
                                        for filename in files:
                                            if ending is None or filename.endswith(ending):
                                                if cnt % 100 == 0:
                                                    print(cnt)
                                                cnt += 1
                                                file_path = os.path.join(root, filename)
                                                data = {}
                                                if file_path.endswith(".gz"):
                                                    with open(file_path, "rb") as file:
                                                        data = json.loads(gzip.decompress(file.read()).decode())
                                                else:
                                                    with open(file_path, "r") as file:
                                                        data = json.load(file)
                                                df = pd.json_normalize(data) # Flatten nested JSON structures
                                                df = df.drop(columns=["proxyVotingRecordsAttached"])
                                                df = df.drop(
                                                    columns=[col for col in df.columns if col.startswith("formData.")]
                                                )
                                                df = df.drop(
                                                    columns=[col for col in df.columns if col.startswith("headerData.")]
                                                )

                                                # explode and flatten proxyVotingRecords
                                                df = df.explode("proxyVotingRecords").reset_index(drop=True)
                                                df = df.join(pd.json_normalize(df["proxyVotingRecords"])).drop(
                                                    columns=["proxyVotingRecords"]
                                                )
                                                dataframes.append(df)

                                    if dataframes:
                                        return pd.concat(dataframes, ignore_index=True)
                                    else:
                                        return pd.DataFrame()


                                proxy_voting_records = load_json_files(DATA_DIR, ending="_proxy_voting_record.json")

                                print(len(proxy_voting_records))
                                proxy_voting_records.head()
                                print(f"Number of proxy voting records = {len(proxy_voting_records):,}")
                                Number of proxy voting records = 24,159,505

                                Vote Categories

                                voteCategories = pd.json_normalize(
                                    proxy_voting_records["voteCategories.voteCategory"].explode()
                                )
                                count_voteCategory = (
                                    voteCategories.groupby(voteCategories["categoryType"].str.upper().str.strip())
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Vote Category")
                                count_voteCategory["Pct"] = (
                                    count_voteCategory["Count"].astype(int)
                                    / count_voteCategory["Count"].astype(int).sum()
                                ).map("{:.2%}".format)

                                count_voteCategory.sort_values(by="Count", ascending=False, inplace=True)
                                count_voteCategory["Count"] = count_voteCategory["Count"].map(lambda x: f"{x:,}")

                                print(f"Number of Voting Records including specific Vote Category (2024)\n")

                                count_voteCategory.head(14)
                                Number of Voting Records including specific Vote Category (2024)

                                Out[29]:
                                CountPct
                                Vote Category
                                DIRECTOR ELECTIONS12,496,73349.20%
                                CORPORATE GOVERNANCE3,179,13512.52%
                                SECTION 14A SAY-ON-PAY VOTES3,042,31511.98%
                                COMPENSATION1,855,0677.30%
                                CAPITAL STRUCTURE1,740,0066.85%
                                AUDIT-RELATED1,598,3876.29%
                                OTHER458,4031.80%
                                OTHER SOCIAL ISSUES307,1591.21%
                                ENVIRONMENT OR CLIMATE205,5120.81%
                                EXTRAORDINARY TRANSACTIONS178,8270.70%
                                HUMAN RIGHTS OR HUMAN CAPITAL/WORKFORCE120,5010.47%
                                DIVERSITY, EQUITY, AND INCLUSION118,2460.47%
                                SHAREHOLDER RIGHTS AND DEFENSES97,8240.39%
                                INVESTMENT COMPANY MATTERS3,2370.01%

                                Shares on Loan

                                ## compare the number of sharesOnLoan to number of sharesVoted on a company by company basis

                                shares_voted_and_on_loan = proxy_voting_records[
                                    ["cusip", "sharesVoted", "sharesOnLoan"]
                                ]
                                shares_voted_and_on_loan.loc[:, "pctSharesVoted"] = (
                                    shares_voted_and_on_loan["sharesOnLoan"] / shares_voted_and_on_loan["sharesVoted"]
                                )
                                shares_voted_and_on_loan["pctSharesVoted"].clip(0, 1).plot(
                                    kind="hist",
                                    bins=20,
                                    color="steelblue",
                                    edgecolor="black",
                                    linewidth=0.5,
                                    figsize=(3.5, 2),
                                )
                                plt.grid(axis="x")
                                plt.title("Shares Voted that are on loan (2024)")
                                plt.xlabel("Percentage of Shares Voted")
                                plt.ylabel("Number of Voting Records")
                                plt.yscale("log")

                                Distribution of votes among filers

                                # prepare a mapping from cik to company name
                                df_cik_names = structured_data[["cik", "companyName"]].drop_duplicates(["cik"])
                                company_names_by_cik = {}
                                for row in df_cik_names.values:
                                    company_names_by_cik[f"{row[0]}"] = row[1]


                                def get_company_name(cik):
                                    return company_names_by_cik.get(f"{cik}", "Unknown")
                                count_voteByFiler = (
                                    proxy_voting_records.groupby(proxy_voting_records["cik"])
                                    .size()
                                    .to_frame(name="Count")
                                ).rename_axis("Filer")
                                count_voteByFiler["Pct"] = (
                                    count_voteByFiler["Count"].astype(int)
                                    / count_voteByFiler["Count"].astype(int).sum()
                                ).map("{:.2%}".format)

                                count_voteByFiler.sort_values(by="Count", ascending=False, inplace=True)
                                count_voteByFiler["Count"] = count_voteByFiler["Count"].map(lambda x: f"{x:,}")

                                count_voteByFiler.index = count_voteByFiler.index.map(
                                    lambda x: f"{get_company_name(x)} ({x})"
                                )

                                print(f"Number of Voting Records by Company (2024)\n")

                                count_voteByFiler.head(15)
                                Number of Voting Records by Company (2024)

                                Out[33]:
                                CountPct
                                Filer
                                iSHARES TRUST (1100663)1,141,5594.73%
                                iShares, Inc. (930667)513,5922.13%
                                DFA INVESTMENT DIMENSIONS GROUP INC (355437)486,2752.01%
                                EQ ADVISORS TRUST (1027263)474,5741.96%
                                Dimensional ETF Trust (1816125)468,2371.94%
                                AMERICAN CENTURY ETF TRUST (1710607)440,6651.82%
                                FIDELITY SALEM STREET TRUST (35315)429,8771.78%
                                VANGUARD INTERNATIONAL EQUITY INDEX FUNDS (857489)401,3431.66%
                                SPDR INDEX SHARES FUNDS (1168164)386,4581.60%
                                RUSSELL INVESTMENT CO (351601)358,2201.48%
                                JNL SERIES TRUST (933691)311,3141.29%
                                FIDELITY CONCORD STREET TRUST (819118)294,9821.22%
                                WisdomTree Trust (1350487)293,6091.22%
                                ADVANCED SERIES TRUST (814679)288,9901.20%
                                Global X Funds (1432353)252,2981.04%

                                Votes for specific company - example Tesla

                                In this section, we aggregate all reported proxy votes for a specific company as identified by its ticker symbol.

                                Form N-PX proxy voting records do not include the ticker symbol of the company holding the shareholder meeting, so we first obtain cusip via the sec-api.io Mapping API and then query for all records with the given cusip.

                                from sec_api import MappingApi

                                mappingApi = MappingApi(api_key="YOUR_API_KEY")

                                cusip_tesla = mappingApi.resolve("ticker", "^TSLA$")[0]["cusip"]
                                print(cusip_tesla)

                                proxyVotes_tesla = proxy_voting_records[proxy_voting_records["cusip"] == cusip_tesla]
                                88160R101

                                Extract the proxy voting records for which the meeting date matches the 2024 shareholder meeting date of June, 6.

                                # extract meetingYear, meetingMonth, meetingDay from meetingDate
                                proxyVotes_tesla.loc[:, "meetingYear"] = pd.to_numeric(
                                    proxyVotes_tesla["meetingDate"].str.extract(r"(\d{1,2})/(\d{1,2})/(\d{2,4})")[2],
                                    errors="coerce",
                                ).astype("Int64")
                                # add 2000 to meeting years less than 100
                                proxyVotes_tesla.loc[:, "meetingYear"] = proxyVotes_tesla["meetingYear"].apply(
                                    lambda x: x + 2000 if x < 100 else x
                                )
                                proxyVotes_tesla.loc[:, "meetingMonth"] = pd.to_numeric(
                                    proxyVotes_tesla["meetingDate"].str.extract(r"(\d{1,2})/(\d{1,2})/(\d{2,4})")[0],
                                    errors="coerce",
                                ).astype("Int64")

                                proxyVotes_tesla.loc[:, "meetingDay"] = pd.to_numeric(
                                    proxyVotes_tesla["meetingDate"].str.extract(r"(\d{1,2})/(\d{1,2})/(\d{2,4})")[1],
                                    errors="coerce",
                                ).astype("Int64")
                                # count the number of values of meetingDate
                                count_meetingDate_tesla = (
                                    proxyVotes_tesla.groupby(["meetingYear", "meetingMonth", "meetingDay"])
                                    .size()
                                    .to_frame(name="Count")
                                    .reset_index()
                                )
                                count_meetingDate_tesla.sort_values(
                                    "Count", ascending=False, inplace=True, ignore_index=True
                                )

                                print(
                                    f"Number of Proxy Voting Records by Meeting Date for Tesla shareholder meeting (2024)\n"
                                )
                                count_meetingDate_tesla.head(10)
                                Number of Proxy Voting Records by Meeting Date for Tesla shareholder meeting (2024)

                                Out[36]:
                                meetingYearmeetingMonthmeetingDayCount
                                0202461317941
                                1202461464
                                2202461215
                                3202413615
                                4202451314
                                5202441513
                                620235166
                                720245156
                                820245265
                                920246245
                                # only keep rows in meetings_tesla where the meetingYear is 2024, meetingDay is 13, and meetingMonth is 6
                                proxyVotes_tesla = proxyVotes_tesla[
                                    (proxyVotes_tesla["meetingYear"] == 2024)
                                    & (proxyVotes_tesla["meetingMonth"] == 6)
                                    & (proxyVotes_tesla["meetingDay"] == 13)
                                ]

                                Some of the proxy voting records contain erroneous values for the number of shares voted, off by a factor of a 1,000,000. For the present demonstration, we will therefore simply ignore all voting records from companies for which an N-PX/A filing was published in 2024, indicating that there were errors in the original filing.

                                # first get ciks for which an N-PX/A filing was made indicating errors in the initial filing
                                ciks_with_errors = structured_data[structured_data["formType"] == "N-PX/A"][
                                    "cik"
                                ].unique()

                                proxyVotes_tesla = proxyVotes_tesla[~proxyVotes_tesla["cik"].isin(ciks_with_errors)]
                                len(proxyVotes_tesla)
                                Out[38]:
                                17941
                                count_voteByFiler_tesla = (
                                    proxyVotes_tesla.groupby(proxyVotes_tesla["cik"]).size().to_frame(name="Count")
                                ).rename_axis("Filer")

                                # store a dict for later
                                vote_count_by_cik_tesla = count_voteByFiler_tesla["Count"].to_dict()

                                count_voteByFiler_tesla["Pct"] = (
                                    count_voteByFiler_tesla["Count"].astype(int)
                                    / count_voteByFiler_tesla["Count"].astype(int).sum()
                                ).map("{:.2%}".format)

                                count_voteByFiler_tesla.sort_values(by="Count", ascending=False, inplace=True)
                                count_voteByFiler_tesla["Count"] = count_voteByFiler_tesla["Count"].map(
                                    lambda x: f"{x:,}"
                                )
                                count_voteByFiler_tesla.index = count_voteByFiler_tesla.index.map(
                                    lambda x: f"{get_company_name(x)} ({x})"
                                )

                                print(f"Number of Proxy Voting Records by Filer for Tesla shareholder meeting (2024)\n")

                                count_voteByFiler_tesla.head(15)
                                Number of Proxy Voting Records by Filer for Tesla shareholder meeting (2024)

                                Out[39]:
                                CountPct
                                Filer
                                iSHARES TRUST (1100663)4812.68%
                                Global X Funds (1432353)4682.61%
                                EQ ADVISORS TRUST (1027263)3121.74%
                                Natixis Funds Trust IV (1095726)2861.59%
                                ADVANCED SERIES TRUST (814679)2731.52%
                                JNL SERIES TRUST (933691)2211.23%
                                RUSSELL INVESTMENT CO (351601)1951.09%
                                SPDR SERIES TRUST (1064642)1951.09%
                                PROSHARES TRUST (1174610)1690.94%
                                SUNAMERICA SERIES TRUST (892538)1680.94%
                                VANGUARD WORLD FUND (52848)1570.88%
                                ProFunds (1039803)1560.87%
                                LINCOLN VARIABLE INSURANCE PRODUCTS TRUST (914036)1430.80%
                                THRIVENT SERIES FUND INC (790166)1430.80%
                                Members Trust Co (1677560)1430.80%

                                Some companies misreported their number of voted shares by a factor of 1,000,000 and have not filed an amendment yet, so they are still part of the dataframe. These were all small funds, but due to erroneous reporting, they appeared as the largest funds.

                                As a simple remedy, we ignore them by requesting that the fund reported at least 10 voting records for the Tesla shareholder meeting.

                                # apply the counts for filtering
                                proxyVotes_tesla["voteCount"] = proxyVotes_tesla["cik"].apply(
                                    lambda x: vote_count_by_cik_tesla[x]
                                )
                                proxyVotes_tesla = proxyVotes_tesla[proxyVotes_tesla["voteCount"] > 10]
                                count_numberOfVotesByFiler_tesla = (
                                    proxyVotes_tesla.groupby("cik").agg({"sharesVoted": "sum"})
                                ).rename_axis("Fund")
                                count_numberOfVotesByFiler_tesla["Pct"] = (
                                    count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int)
                                    / count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int).sum()
                                ).map("{:.2%}".format)

                                # count_numberOfVotesByFiler_tesla.sort_values(by="Count", ascending=False, inplace=True)
                                count_numberOfVotesByFiler_tesla.sort_values(
                                    by="sharesVoted", ascending=False, inplace=True
                                )
                                count_numberOfVotesByFiler_tesla["sharesVoted"] = count_numberOfVotesByFiler_tesla[
                                    "sharesVoted"
                                ].map(lambda x: f"{int(round(x)):,}")

                                count_numberOfVotesByFiler_tesla.index = count_numberOfVotesByFiler_tesla.index.map(
                                    lambda x: f"{get_company_name(x)} ({x})"
                                )


                                print(f"Number of Shares Voted by Fund at Tesla shareholder meeting (2024)\n")

                                count_numberOfVotesByFiler_tesla.head(15)
                                Number of Shares Voted by Fund at Tesla shareholder meeting (2024)

                                Out[42]:
                                sharesVotedPct
                                Fund
                                WisdomTree Digital Trust (1859001)5,681,011,76542.84%
                                VANGUARD INDEX FUNDS (36405)2,392,883,22018.04%
                                iSHARES TRUST (1100663)743,331,8075.61%
                                FIDELITY CONCORD STREET TRUST (819118)599,545,4404.52%
                                GROWTH FUND OF AMERICA (44201)282,407,4892.13%
                                VANGUARD INSTITUTIONAL INDEX FUNDS (862084)258,425,7521.95%
                                SELECT SECTOR SPDR TRUST (1064641)195,788,2031.48%
                                BARON SELECT FUNDS (1217673)159,618,7581.20%
                                NEW PERSPECTIVE FUND (71516)138,173,0091.04%
                                VANGUARD WORLD FUND (52848)122,948,0030.93%
                                AMERICAN FUNDS INSURANCE SERIES (729528)104,891,9950.79%
                                SCHWAB CAPITAL TRUST (904333)95,155,6970.72%
                                SCHWAB STRATEGIC TRUST (1454889)93,492,4120.71%
                                BLACKROCK FINANCIAL MANAGEMENT INC/DE (1086363)86,251,4510.65%
                                VANGUARD CHESTER FUNDS (752177)79,720,2000.60%

                                Voting behavior of large funds

                                # aggregate the AGAINST, FOR, ABSTAIN, and WITHHOLD votes for each row in proxyVotes_tesla
                                # strip the strings and convert to uppercase
                                for voteDirection in ["For", "Against", "Abstain", "Withhold"]:
                                    proxyVotes_tesla[f"sharesVoted{voteDirection}"] = proxyVotes_tesla[
                                        "vote.voteRecord"
                                    ].apply(
                                        lambda x: (
                                            0
                                            if isinstance(x, float)
                                            else sum(
                                                [
                                                    (
                                                        vote["sharesVoted"]
                                                        if "howVoted" in vote
                                                        and "sharesVoted" in vote
                                                        and vote["howVoted"].strip().upper() == voteDirection.upper()
                                                        else 0
                                                    )
                                                    for vote in x
                                                ]
                                            )
                                        )
                                    )
                                    proxyVotes_tesla[f"sharesVoted{voteDirection}Pct"] = (
                                        proxyVotes_tesla[f"sharesVoted{voteDirection}"]
                                        / proxyVotes_tesla["sharesVoted"]
                                    ).map("{:.2%}".format)
                                # count the number of votes that were cast in favor of management suggestion
                                # only count votes where the management recommendation is FOR or AGAINST
                                proxyVotes_tesla[f"sharesVotedWithManagement"] = proxyVotes_tesla[
                                    "vote.voteRecord"
                                ].apply(
                                    lambda x: (
                                        0
                                        if isinstance(x, float)
                                        else sum(
                                            [
                                                (
                                                    vote["sharesVoted"]
                                                    if "howVoted" in vote
                                                    and "sharesVoted" in vote
                                                    and "managementRecommendation" in vote
                                                    and vote["managementRecommendation"].strip().upper()
                                                    in ["FOR", "AGAINST"]
                                                    and vote["howVoted"].strip().upper()
                                                    == vote["managementRecommendation"].strip().upper()
                                                    else 0
                                                )
                                                for vote in x
                                            ]
                                        )
                                    )
                                )
                                proxyVotes_tesla[f"sharesVotedWithManagementPct"] = (
                                    proxyVotes_tesla[f"sharesVotedWithManagement"] / proxyVotes_tesla["sharesVoted"]
                                ).map("{:.2%}".format)
                                # check if the votes are cast either all for for, against, abstain, or withhold
                                # i.e. check if any of the rows of sharesVotedFor, sharesVotedAgainst,
                                # sharesVotedAbstain, sharesVotedWithhold are equal to sharesVoted
                                proxyVotes_tesla.loc[:, "unidirectionalVote"] = proxyVotes_tesla[
                                    "sharesVoted"
                                ] == proxyVotes_tesla[
                                    [
                                        "sharesVotedFor",
                                        "sharesVotedAgainst",
                                        "sharesVotedAbstain",
                                        "sharesVotedWithhold",
                                    ]
                                ].sum(
                                    axis=1
                                )

                                print("Number of Unidirectional Votes by Funds at the 2024 Tesla Shareholder Meeting")
                                proxyVotes_tesla["unidirectionalVote"].value_counts()
                                Number of Unidirectional Votes by Funds at the 2024 Tesla Shareholder Meeting
                                Out:
                                unidirectionalVote
                                True 16067
                                False 161
                                Name: count, dtype: int64

                                The following code explores the voting behavior of the largest funds at the Tesla shareholder meeting 2024. The exploration is based on the number of shares voted and focus on the votes cast for or against proposals, abstained and withhold votes. Additionally, the percentage of voted shares in accordance with management suggestions is analyzed. Note that the number of shares is summed over all proposals, thus, the number of shares voted does not represent the number of shares held by the fund.

                                count_numberOfVotesByFiler_tesla = (
                                    proxyVotes_tesla.groupby(proxyVotes_tesla["cik"]).agg(
                                        {
                                            "sharesVoted": "sum",
                                            "sharesVotedFor": "sum",
                                            "sharesVotedAgainst": "sum",
                                            "sharesVotedAbstain": "sum",
                                            "sharesVotedWithhold": "sum",
                                            "sharesVotedWithManagement": "sum",
                                        }
                                    )
                                ).rename_axis("Fund")

                                count_numberOfVotesByFiler_tesla["Pct"] = (
                                    count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int)
                                    / count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int).sum()
                                ).map("{:.2%}".format)

                                for voteDirection in ["For", "Against", "Abstain", "Withhold", "WithManagement"]:
                                    count_numberOfVotesByFiler_tesla[f"Vote{voteDirection}Pct"] = (
                                        count_numberOfVotesByFiler_tesla[f"sharesVoted{voteDirection}"].astype(int)
                                        / count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int)
                                    ).map("{:.2%}".format)

                                count_numberOfVotesByFiler_tesla["Pct"] = (
                                    count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int)
                                    / count_numberOfVotesByFiler_tesla["sharesVoted"].astype(int).sum()
                                ).map("{:.2%}".format)

                                # count_numberOfVotesByFiler_tesla.sort_values(by="Count", ascending=False, inplace=True)
                                count_numberOfVotesByFiler_tesla.sort_values(
                                    by="sharesVoted", ascending=False, inplace=True
                                )
                                count_numberOfVotesByFiler_tesla["sharesVoted"] = count_numberOfVotesByFiler_tesla[
                                    "sharesVoted"
                                ].map(lambda x: f"{int(round(x)):,}")
                                count_numberOfVotesByFiler_tesla.index = count_numberOfVotesByFiler_tesla.index.map(
                                    lambda x: f"{get_company_name(x)} ({x})"
                                )


                                print(f"Direction of Votes by Company at Tesla Shareholder Meeting (2024)\n")

                                count_numberOfVotesByFiler_tesla[
                                    [
                                        "sharesVoted",
                                        "Pct",
                                        "VoteForPct",
                                        "VoteAgainstPct",
                                        "VoteWithholdPct",
                                        "VoteAbstainPct",
                                        "VoteWithManagementPct",
                                    ]
                                ].head(40)
                                Direction of Votes by Company at Tesla Shareholder Meeting (2024)

                                Out[45]:
                                sharesVotedPctVoteForPctVoteAgainstPctVoteWithholdPctVoteAbstainPctVoteWithManagementPct
                                Fund
                                WisdomTree Digital Trust (1859001)5,681,011,76542.84%61.54%38.46%0.00%0.00%46.15%
                                VANGUARD INDEX FUNDS (36405)2,392,883,22018.04%61.54%38.46%0.00%0.00%46.15%
                                iSHARES TRUST (1100663)743,331,8075.61%61.51%38.49%0.00%0.00%46.15%
                                FIDELITY CONCORD STREET TRUST (819118)599,545,4404.52%61.54%38.46%0.00%0.00%46.15%
                                GROWTH FUND OF AMERICA (44201)282,407,4892.13%58.45%41.55%0.00%0.00%46.15%
                                VANGUARD INSTITUTIONAL INDEX FUNDS (862084)258,425,7521.95%61.54%38.46%0.00%0.00%46.15%
                                SELECT SECTOR SPDR TRUST (1064641)195,788,2031.48%30.77%69.23%0.00%0.00%46.15%
                                BARON SELECT FUNDS (1217673)159,618,7581.20%61.54%38.46%0.00%0.00%46.15%
                                NEW PERSPECTIVE FUND (71516)138,173,0091.04%61.31%38.69%0.00%0.00%46.15%
                                VANGUARD WORLD FUND (52848)122,948,0030.93%61.85%38.15%0.00%0.00%46.64%
                                AMERICAN FUNDS INSURANCE SERIES (729528)104,891,9950.79%60.58%39.42%0.00%0.00%46.15%
                                SCHWAB CAPITAL TRUST (904333)95,155,6970.72%46.15%53.85%0.00%0.00%46.15%
                                SCHWAB STRATEGIC TRUST (1454889)93,492,4120.71%46.15%53.85%0.00%0.00%46.15%
                                BLACKROCK FINANCIAL MANAGEMENT INC/DE (1086363)86,251,4510.65%98.30%1.70%0.00%0.00%100.00%
                                VANGUARD CHESTER FUNDS (752177)79,720,2000.60%50.00%50.00%0.00%0.00%50.00%
                                SPDR SERIES TRUST (1064642)75,977,3040.57%31.14%68.86%0.00%0.00%46.15%
                                ARK ETF Trust (1579982)68,360,5910.52%46.15%53.85%0.00%0.00%46.15%
                                COLLEGE RETIREMENT EQUITIES FUND (777535)68,116,7110.51%46.15%53.85%0.00%0.00%46.15%
                                TIAA-CREF FUNDS (1084380)64,992,3170.49%46.15%53.85%0.00%0.00%46.15%
                                JPMORGAN TRUST II (763852)64,622,0120.49%52.88%47.12%0.00%0.00%46.15%
                                AMERICAN CENTURY MUTUAL FUNDS, INC. (100334)63,618,0610.48%48.48%51.52%0.00%0.00%46.15%
                                T. Rowe Price Blue Chip Growth Fund, Inc. (902259)61,823,3070.47%30.77%61.54%0.00%7.69%38.46%
                                FIDELITY MT VERNON STREET TRUST (707823)55,773,3020.42%38.46%61.54%0.00%0.00%46.15%
                                MASTER INVESTMENT PORTFOLIO (915092)54,364,9600.41%61.54%38.46%0.00%0.00%46.15%
                                Global X Funds (1432353)53,479,4780.40%53.87%46.13%0.00%0.00%46.15%
                                LOOMIS SAYLES FUNDS II (872649)51,756,7830.39%53.85%46.15%0.00%0.00%46.15%
                                FIDELITY SECURITIES FUND (754510)50,857,3910.38%38.46%61.54%0.00%0.00%46.15%
                                JNL SERIES TRUST (933691)50,683,9190.38%59.92%40.08%0.00%0.00%46.15%
                                FIDELITY SALEM STREET TRUST (35315)49,572,2370.37%61.54%38.46%0.00%0.00%46.15%
                                Invesco Exchange-Traded Fund Trust II (1378872)47,468,2650.36%69.23%30.77%0.00%0.00%46.15%
                                VANGUARD SCOTTSDALE FUNDS (1021882)43,774,3800.33%61.53%38.47%0.00%0.00%46.15%
                                Fidelity Rutland Square Trust II (1364924)42,461,9520.32%42.49%57.51%0.00%0.00%46.15%
                                EQ ADVISORS TRUST (1027263)38,556,1150.29%48.77%51.23%0.00%0.00%46.15%
                                HARBOR FUNDS (793769)31,847,2440.24%61.53%38.47%0.00%0.00%46.15%
                                Franklin Custodian Funds (38721)29,555,2790.22%46.15%53.85%0.00%0.00%46.15%
                                PROSHARES TRUST (1174610)29,305,9520.22%53.85%46.15%0.00%0.00%46.15%
                                T. Rowe Price Index Trust, Inc. (858581)27,110,0960.20%38.46%61.54%0.00%0.00%46.15%
                                VICTORY PORTFOLIOS III (908695)25,553,6970.19%53.88%46.12%0.00%0.00%46.15%
                                VANGUARD VALLEY FORGE FUNDS (889519)23,328,7340.18%61.54%38.46%0.00%0.00%46.15%
                                Virtus Artificial Intelligence & Technology Opportunities Fund (1778114)23,059,1900.17%50.00%50.00%0.00%0.00%50.00%

                                Products

                                • MCP Server
                                • Financial Analysis Prompt Library
                                • EDGAR Filing Search API
                                • Full-Text Search API
                                • Real-Time Filing Stream API
                                • EDGAR File & Filing Download API
                                • Filing to PDF Generator API
                                • XBRL-to-JSON Converter
                                • 10-K/10-Q/8-K Item Extractor
                                • Investment Adviser & Form ADV API
                                • Insider Trading Data - Form 3, 4, 5
                                • Restricted Sales Notifications - Form 144
                                • Institutional Holdings - Form 13F
                                • Form N-PORT API - Investment Company Holdings
                                • Form N-CEN API - Annual Reports by Investment Companies
                                • Form N-PX API - Proxy Voting Records
                                • Form 13D/13G API
                                • Form S-1/424B4 - IPOs, Debt & Rights Offerings
                                • Form C - Crowdfunding Offerings
                                • Form D - Private Placements & Exempt Offerings
                                • Regulation A Offering Statements API
                                • Changes in Auditors & Accountants
                                • Non-Reliance on Prior Financial Statements
                                • Executive Compensation Data API
                                • Audit Fees Data API
                                • Directors & Board Members Data
                                • Company Subsidiaries Database
                                • Outstanding Shares & Public Float
                                • SEC Enforcement Actions
                                • Accounting & Auditing Enforcement Releases (AAERs)
                                • SRO Filings
                                • CIK, CUSIP, Ticker Mapping
                                • Bulk Datasets

                                General

                                • Pricing
                                • APIs
                                • Datasets
                                • Data Browser
                                • Features
                                • Supported Filings
                                • EDGAR Filing Statistics
                                • Equity Research & Market Insights

                                Account

                                • Sign Up - Start Free Trial
                                • Log In
                                • Forgot Password

                                Developers

                                • API Playground
                                • Documentation
                                • API Reference
                                • llms.txt
                                • Resources & Tutorials
                                • Python API SDK
                                • Node.js API SDK
                                • Cookbook
                                • GitHub

                                Legal

                                • Terms of Service
                                • Privacy Policy

                                Legal

                                • Terms of Service
                                • Privacy Policy

                                SEC API

                                © 2019 - 2026 sec-api.io by Data2Value GmbH. All rights reserved.

                                SEC® and EDGAR® are registered trademarks of the U.S. Securities and Exchange Commission (SEC).

                                EDGAR is the Electronic Data Gathering, Analysis, and Retrieval system operated by the SEC.

                                sec-api.io and Data2Value GmbH are independent of, and not affiliated with, sponsored by, or endorsed by the U.S. Securities and Exchange Commission.

                                sec-api.io is classified under SIC code 7375 (Information Retrieval Services), providing on-demand access to structured data and online information services.