ApexCodeCoverage has a row per test method. If you were to add
TestMethodName to your SOQL query you would see the test case that is responsible for the NumLinesCovered.
NumLinesCovered and NumLinesUncovered vary as each test method touches different lines.
You can drill further down to Coverage to see specifically which lines were hit and which weren't.
Instead you might want to use the SOQL queries that the developer console does:
ApexCodeCoverageAggregate - Represents aggregate code coverage test results for an Apex class or trigger.
SELECT ApexClassOrTriggerId, ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered FROM ApexCodeCoverageAggregate WHERE ApexClassOrTriggerId != NULL AND ApexClassOrTrigger.Name != NULL AND (NumLinesCovered > 0 OR NumLinesUncovered > 0) AND NumLinesCovered != NULL AND NumLinesUncovered != NULL ORDER BY ApexClassOrTrigger.Name
ApexOrgWideCoverage - Represents code coverage test results for an entire organization.
SELECT PercentCovered FROM ApexOrgWideCoverage
Note: both of these SOQL queries are against the tooling API.